summaryrefslogtreecommitdiff
path: root/builtin/commit.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/commit.c')
-rw-r--r--builtin/commit.c211
1 files changed, 167 insertions, 44 deletions
diff --git a/builtin/commit.c b/builtin/commit.c
index 27d4ff6b79..55d50a8891 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -105,7 +105,8 @@ static const char *template_file;
*/
static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
-static char *fixup_message, *squash_message;
+static char *fixup_message, *fixup_commit, *squash_message;
+static const char *fixup_prefix;
static int all, also, interactive, patch_interactive, only, amend, signoff;
static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
@@ -113,6 +114,7 @@ static int config_commit_verbose = -1; /* unspecified */
static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
static char *sign_commit, *pathspec_from_file;
+static struct strvec trailer_args = STRVEC_INIT;
/*
* The default commit message cleanup mode will remove the lines
@@ -131,6 +133,14 @@ static struct strbuf message = STRBUF_INIT;
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
+static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
+{
+ BUG_ON_OPT_NEG(unset);
+
+ strvec_pushl(&trailer_args, "--trailer", arg, NULL);
+ return 0;
+}
+
static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
{
enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -326,7 +336,7 @@ static void refresh_cache_or_die(int refresh_flags)
die_resolve_conflict("commit");
}
-static const char *prepare_index(int argc, const char **argv, const char *prefix,
+static const char *prepare_index(const char **argv, const char *prefix,
const struct commit *current_head, int is_status)
{
struct string_list partial = STRING_LIST_INIT_DUP;
@@ -357,7 +367,8 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
die(_("--pathspec-file-nul requires --pathspec-from-file"));
}
- if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
+ if (!pathspec.nr && (also || (only && !allow_empty &&
+ (!amend || (fixup_message && strcmp(fixup_prefix, "amend"))))))
die(_("No paths with --include/--only does not make sense."));
if (read_cache_preload(&pathspec) < 0)
@@ -378,7 +389,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT));
setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1);
- if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
+ if (interactive_add(argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
the_repository->index_file = old_repo_index_file;
@@ -681,6 +692,22 @@ static void adjust_comment_line_char(const struct strbuf *sb)
comment_line_char = *p;
}
+static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
+ struct pretty_print_context *ctx)
+{
+ const char *buffer, *subject, *fmt;
+
+ buffer = get_commit_buffer(commit, NULL);
+ find_commit_subject(buffer, &subject);
+ /*
+ * If we amend the 'amend!' commit then we don't want to
+ * duplicate the subject line.
+ */
+ fmt = starts_with(subject, "amend!") ? "%b" : "%B";
+ format_commit_message(commit, fmt, sb, ctx);
+ unuse_commit_buffer(commit, buffer);
+}
+
static int prepare_to_commit(const char *index_file, const char *prefix,
struct commit *current_head,
struct wt_status *s,
@@ -745,15 +772,33 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
} else if (fixup_message) {
struct pretty_print_context ctx = {0};
struct commit *commit;
- commit = lookup_commit_reference_by_name(fixup_message);
+ char *fmt;
+ commit = lookup_commit_reference_by_name(fixup_commit);
if (!commit)
- die(_("could not lookup commit %s"), fixup_message);
+ die(_("could not lookup commit %s"), fixup_commit);
ctx.output_encoding = get_commit_output_encoding();
- format_commit_message(commit, "fixup! %s\n\n",
- &sb, &ctx);
- if (have_option_m)
- strbuf_addbuf(&sb, &message);
+ fmt = xstrfmt("%s! %%s\n\n", fixup_prefix);
+ format_commit_message(commit, fmt, &sb, &ctx);
+ free(fmt);
hook_arg1 = "message";
+
+ /*
+ * Only `-m` commit message option is checked here, as
+ * it supports `--fixup` to append the commit message.
+ *
+ * The other commit message options `-c`/`-C`/`-F` are
+ * incompatible with all the forms of `--fixup` and
+ * have already errored out while parsing the `git commit`
+ * options.
+ */
+ if (have_option_m && !strcmp(fixup_prefix, "fixup"))
+ strbuf_addbuf(&sb, &message);
+
+ if (!strcmp(fixup_prefix, "amend")) {
+ if (have_option_m)
+ die(_("cannot combine -m with --fixup:%s"), fixup_message);
+ prepare_amend_commit(commit, &sb, &ctx);
+ }
} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {
size_t merge_msg_start;
@@ -847,21 +892,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
!merge_contains_scissors)
wt_status_add_cut_line(s->fp);
- status_printf_ln(s, GIT_COLOR_NORMAL,
- whence == FROM_MERGE
- ? _("\n"
- "It looks like you may be committing a merge.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n")
- : _("\n"
- "It looks like you may be committing a cherry-pick.\n"
- "If this is not correct, please remove the file\n"
- " %s\n"
- "and try again.\n"),
+ status_printf_ln(
+ s, GIT_COLOR_NORMAL,
whence == FROM_MERGE ?
- git_path_merge_head(the_repository) :
- git_path_cherry_pick_head(the_repository));
+ _("\n"
+ "It looks like you may be committing a merge.\n"
+ "If this is not correct, please run\n"
+ " git update-ref -d MERGE_HEAD\n"
+ "and try again.\n") :
+ _("\n"
+ "It looks like you may be committing a cherry-pick.\n"
+ "If this is not correct, please run\n"
+ " git update-ref -d CHERRY_PICK_HEAD\n"
+ "and try again.\n"));
}
fprintf(s->fp, "\n");
@@ -960,6 +1003,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fclose(s->fp);
+ if (trailer_args.nr) {
+ struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+ strvec_pushl(&run_trailer.args, "interpret-trailers",
+ "--in-place", git_path_commit_editmsg(), NULL);
+ strvec_pushv(&run_trailer.args, trailer_args.v);
+ run_trailer.git_cmd = 1;
+ if (run_command(&run_trailer))
+ die(_("unable to pass trailers to --trailers"));
+ strvec_clear(&trailer_args);
+ }
+
/*
* Reject an attempt to record a non-merge empty commit without
* explicit --allow-empty. In the cherry-pick case, it may be
@@ -1005,15 +1060,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
return 0;
if (use_editor) {
- struct argv_array env = ARGV_ARRAY_INIT;
+ struct strvec env = STRVEC_INIT;
- argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file);
- if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) {
+ strvec_pushf(&env, "GIT_INDEX_FILE=%s", index_file);
+ if (launch_editor(git_path_commit_editmsg(), NULL, env.v)) {
fprintf(stderr,
_("Please supply the message using either -m or -F option.\n"));
exit(1);
}
- argv_array_clear(&env);
+ strvec_clear(&env);
}
if (!no_verify &&
@@ -1041,7 +1096,7 @@ static const char *find_author_by_nickname(const char *name)
av[++ac] = NULL;
setup_revisions(ac, av, &revs, NULL);
revs.mailmap = &mailmap;
- read_mailmap(revs.mailmap, NULL);
+ read_mailmap(revs.mailmap);
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
@@ -1154,6 +1209,19 @@ static void finalize_deferred_config(struct wt_status *s)
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
}
+static void check_fixup_reword_options(int argc, const char *argv[]) {
+ if (whence != FROM_COMMIT) {
+ if (whence == FROM_MERGE)
+ die(_("You are in the middle of a merge -- cannot reword."));
+ else if (is_from_cherry_pick(whence))
+ die(_("You are in the middle of a cherry-pick -- cannot reword."));
+ }
+ if (argc)
+ die(_("cannot combine reword option of --fixup with path '%s'"), *argv);
+ if (patch_interactive || interactive || all || also || only)
+ die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only"));
+}
+
static int parse_and_validate_options(int argc, const char *argv[],
const struct option *options,
const char * const usage[],
@@ -1172,7 +1240,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && renew_authorship)
die(_("Using both --reset-author and --author does not make sense"));
- if (logfile || have_option_m || use_message || fixup_message)
+ if (logfile || have_option_m || use_message)
use_editor = 0;
if (0 <= edit_flag)
use_editor = edit_flag;
@@ -1229,6 +1297,42 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (also + only + all + interactive > 1)
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
+
+ if (fixup_message) {
+ /*
+ * We limit --fixup's suboptions to only alpha characters.
+ * If the first character after a run of alpha is colon,
+ * then the part before the colon may be a known suboption
+ * name like `amend` or `reword`, or a misspelt suboption
+ * name. In either case, we treat it as
+ * --fixup=<suboption>:<arg>.
+ *
+ * Otherwise, we are dealing with --fixup=<commit>.
+ */
+ char *p = fixup_message;
+ while (isalpha(*p))
+ p++;
+ if (p > fixup_message && *p == ':') {
+ *p = '\0';
+ fixup_commit = p + 1;
+ if (!strcmp("amend", fixup_message) ||
+ !strcmp("reword", fixup_message)) {
+ fixup_prefix = "amend";
+ allow_empty = 1;
+ if (*fixup_message == 'r') {
+ check_fixup_reword_options(argc, argv);
+ only = 1;
+ }
+ } else {
+ die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit);
+ }
+ } else {
+ fixup_commit = fixup_message;
+ fixup_prefix = "fixup";
+ use_editor = 0;
+ }
+ }
+
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
@@ -1243,13 +1347,13 @@ static int parse_and_validate_options(int argc, const char *argv[],
return argc;
}
-static int dry_run_commit(int argc, const char **argv, const char *prefix,
+static int dry_run_commit(const char **argv, const char *prefix,
const struct commit *current_head, struct wt_status *s)
{
int committable;
const char *index_file;
- index_file = prepare_index(argc, argv, prefix, current_head, 1);
+ index_file = prepare_index(argv, prefix, current_head, 1);
committable = run_status(stdout, index_file, prefix, 0, s);
rollback_index_files();
@@ -1372,9 +1476,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
N_("show stash information")),
OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
N_("compute full ahead/behind values")),
- { OPTION_CALLBACK, 0, "porcelain", &status_format,
+ OPT_CALLBACK_F(0, "porcelain", &status_format,
N_("version"), N_("machine-readable output"),
- PARSE_OPT_OPTARG, opt_parse_porcelain },
+ PARSE_OPT_OPTARG, opt_parse_porcelain),
OPT_SET_INT(0, "long", &status_format,
N_("show status in long format (default)"),
STATUS_FORMAT_LONG),
@@ -1393,9 +1497,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")),
- { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg,
+ OPT_CALLBACK_F('M', "find-renames", &rename_score_arg,
N_("n"), N_("detect renames, optionally set similarity index"),
- PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score },
+ PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score),
OPT_END(),
};
@@ -1494,7 +1598,6 @@ static int git_commit_config(const char *k, const char *v, void *cb)
int cmd_commit(int argc, const char **argv, const char *prefix)
{
- const char *argv_gc_auto[] = {"gc", "--auto", NULL};
static struct wt_status s;
static struct option builtin_commit_options[] = {
OPT__QUIET(&quiet, N_("suppress summary after successful commit")),
@@ -1507,10 +1610,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
- OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
+ /*
+ * TRANSLATORS: Leave "[(amend|reword):]" as-is,
+ * and only translate <commit>.
+ */
+ OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
- OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
+ OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
+ OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
OPT_CLEANUP(&cleanup_arg),
@@ -1587,8 +1695,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose;
if (dry_run)
- return dry_run_commit(argc, argv, prefix, current_head, &s);
- index_file = prepare_index(argc, argv, prefix, current_head, 0);
+ return dry_run_commit(argv, prefix, current_head, &s);
+ index_file = prepare_index(argv, prefix, current_head, 0);
/* Set up everything for writing the commit object. This includes
running hooks, writing the trees, and interacting with the user. */
@@ -1666,6 +1774,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
exit(1);
}
+ if (fixup_message && starts_with(sb.buf, "amend! ") &&
+ !allow_empty_message) {
+ struct strbuf body = STRBUF_INIT;
+ size_t len = commit_subject_length(sb.buf);
+ strbuf_addstr(&body, sb.buf + len);
+ if (message_is_empty(&body, cleanup_mode)) {
+ rollback_index_files();
+ fprintf(stderr, _("Aborting commit due to empty commit message body.\n"));
+ exit(1);
+ }
+ strbuf_release(&body);
+ }
+
if (amend) {
const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
@@ -1675,8 +1796,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
}
if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid,
- parents, &oid, author_ident.buf, sign_commit,
- extra)) {
+ parents, &oid, author_ident.buf, NULL,
+ sign_commit, extra)) {
rollback_index_files();
die(_("failed to write commit object"));
}
@@ -1703,7 +1824,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
git_test_write_commit_graph_or_die();
repo_rerere(the_repository, 0);
- run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ run_auto_maintenance(quiet);
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
if (amend && !no_post_rewrite) {
commit_post_rewrite(the_repository, current_head, &oid);
@@ -1719,6 +1840,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
&oid, flags);
}
+ apply_autostash(git_path_merge_autostash(the_repository));
+
UNLEAK(err);
UNLEAK(sb);
return 0;