Merge branch 'cm/rebase-i-fixup-amend-reword'

"git commit --fixup=<commit>", which was to tweak the changes made
to the contents while keeping the original log message intact,
learned "--fixup=(amend|reword):<commit>", that can be used to
tweak both the message and the contents, and only the message,
respectively.

* cm/rebase-i-fixup-amend-reword:
  doc/git-commit: add documentation for fixup=[amend|reword] options
  t3437: use --fixup with options to create amend! commit
  t7500: add tests for --fixup=[amend|reword] options
  commit: add a reword suboption to --fixup
  commit: add amend suboption to --fixup to create amend! commit
  sequencer: export and rename subject_length()
This commit is contained in:
Junio C Hamano
2021-03-26 14:59:03 -07:00
8 changed files with 342 additions and 68 deletions

View File

@ -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;
@ -357,7 +358,8 @@ static const char *prepare_index(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)
@ -681,6 +683,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 +763,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;
@ -1152,6 +1188,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[],
@ -1170,7 +1219,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;
@ -1227,6 +1276,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);
@ -1504,7 +1589,11 @@ 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 a Signed-off-by trailer")),
@ -1663,6 +1752,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);