Skip to content

Commit 89519f6

Browse files
committed
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()
2 parents fde07fc + 00ea64e commit 89519f6

File tree

8 files changed

+342
-68
lines changed

8 files changed

+342
-68
lines changed

Documentation/git-commit.txt

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git commit' [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
12-
[--dry-run] [(-c | -C | --fixup | --squash) <commit>]
12+
[--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]
1313
[-F <file> | -m <msg>] [--reset-author] [--allow-empty]
1414
[--allow-empty-message] [--no-verify] [-e] [--author=<author>]
1515
[--date=<date>] [--cleanup=<mode>] [--[no-]status]
@@ -86,11 +86,44 @@ OPTIONS
8686
Like '-C', but with `-c` the editor is invoked, so that
8787
the user can further edit the commit message.
8888

89-
--fixup=<commit>::
90-
Construct a commit message for use with `rebase --autosquash`.
91-
The commit message will be the subject line from the specified
92-
commit with a prefix of "fixup! ". See linkgit:git-rebase[1]
93-
for details.
89+
--fixup=[(amend|reword):]<commit>::
90+
Create a new commit which "fixes up" `<commit>` when applied with
91+
`git rebase --autosquash`. Plain `--fixup=<commit>` creates a
92+
"fixup!" commit which changes the content of `<commit>` but leaves
93+
its log message untouched. `--fixup=amend:<commit>` is similar but
94+
creates an "amend!" commit which also replaces the log message of
95+
`<commit>` with the log message of the "amend!" commit.
96+
`--fixup=reword:<commit>` creates an "amend!" commit which
97+
replaces the log message of `<commit>` with its own log message
98+
but makes no changes to the content of `<commit>`.
99+
+
100+
The commit created by plain `--fixup=<commit>` has a subject
101+
composed of "fixup!" followed by the subject line from <commit>,
102+
and is recognized specially by `git rebase --autosquash`. The `-m`
103+
option may be used to supplement the log message of the created
104+
commit, but the additional commentary will be thrown away once the
105+
"fixup!" commit is squashed into `<commit>` by
106+
`git rebase --autosquash`.
107+
+
108+
The commit created by `--fixup=amend:<commit>` is similar but its
109+
subject is instead prefixed with "amend!". The log message of
110+
<commit> is copied into the log message of the "amend!" commit and
111+
opened in an editor so it can be refined. When `git rebase
112+
--autosquash` squashes the "amend!" commit into `<commit>`, the
113+
log message of `<commit>` is replaced by the refined log message
114+
from the "amend!" commit. It is an error for the "amend!" commit's
115+
log message to be empty unless `--allow-empty-message` is
116+
specified.
117+
+
118+
`--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>
119+
--only`. It creates an "amend!" commit with only a log message
120+
(ignoring any changes staged in the index). When squashed by `git
121+
rebase --autosquash`, it replaces the log message of `<commit>`
122+
without making any other changes.
123+
+
124+
Neither "fixup!" nor "amend!" commits change authorship of
125+
`<commit>` when applied by `git rebase --autosquash`.
126+
See linkgit:git-rebase[1] for details.
94127

95128
--squash=<commit>::
96129
Construct a commit message for use with `rebase --autosquash`.

Documentation/git-rebase.txt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -593,16 +593,17 @@ See also INCOMPATIBLE OPTIONS below.
593593

594594
--autosquash::
595595
--no-autosquash::
596-
When the commit log message begins with "squash! ..." (or
597-
"fixup! ..."), and there is already a commit in the todo list that
598-
matches the same `...`, automatically modify the todo list of rebase
599-
-i so that the commit marked for squashing comes right after the
600-
commit to be modified, and change the action of the moved commit
601-
from `pick` to `squash` (or `fixup`). A commit matches the `...` if
602-
the commit subject matches, or if the `...` refers to the commit's
603-
hash. As a fall-back, partial matches of the commit subject work,
604-
too. The recommended way to create fixup/squash commits is by using
605-
the `--fixup`/`--squash` options of linkgit:git-commit[1].
596+
When the commit log message begins with "squash! ..." or "fixup! ..."
597+
or "amend! ...", and there is already a commit in the todo list that
598+
matches the same `...`, automatically modify the todo list of
599+
`rebase -i`, so that the commit marked for squashing comes right after
600+
the commit to be modified, and change the action of the moved commit
601+
from `pick` to `squash` or `fixup` or `fixup -C` respectively. A commit
602+
matches the `...` if the commit subject matches, or if the `...` refers
603+
to the commit's hash. As a fall-back, partial matches of the commit
604+
subject work, too. The recommended way to create fixup/amend/squash
605+
commits is by using the `--fixup`, `--fixup=amend:` or `--fixup=reword:`
606+
and `--squash` options respectively of linkgit:git-commit[1].
606607
+
607608
If the `--autosquash` option is enabled by default using the
608609
configuration variable `rebase.autoSquash`, this option can be

builtin/commit.c

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ static const char *template_file;
105105
*/
106106
static const char *author_message, *author_message_buffer;
107107
static char *edit_message, *use_message;
108-
static char *fixup_message, *squash_message;
108+
static char *fixup_message, *fixup_commit, *squash_message;
109+
static const char *fixup_prefix;
109110
static int all, also, interactive, patch_interactive, only, amend, signoff;
110111
static int edit_flag = -1; /* unspecified */
111112
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,
357358
die(_("--pathspec-file-nul requires --pathspec-from-file"));
358359
}
359360

360-
if (!pathspec.nr && (also || (only && !amend && !allow_empty)))
361+
if (!pathspec.nr && (also || (only && !allow_empty &&
362+
(!amend || (fixup_message && strcmp(fixup_prefix, "amend"))))))
361363
die(_("No paths with --include/--only does not make sense."));
362364

363365
if (read_cache_preload(&pathspec) < 0)
@@ -681,6 +683,22 @@ static void adjust_comment_line_char(const struct strbuf *sb)
681683
comment_line_char = *p;
682684
}
683685

686+
static void prepare_amend_commit(struct commit *commit, struct strbuf *sb,
687+
struct pretty_print_context *ctx)
688+
{
689+
const char *buffer, *subject, *fmt;
690+
691+
buffer = get_commit_buffer(commit, NULL);
692+
find_commit_subject(buffer, &subject);
693+
/*
694+
* If we amend the 'amend!' commit then we don't want to
695+
* duplicate the subject line.
696+
*/
697+
fmt = starts_with(subject, "amend!") ? "%b" : "%B";
698+
format_commit_message(commit, fmt, sb, ctx);
699+
unuse_commit_buffer(commit, buffer);
700+
}
701+
684702
static int prepare_to_commit(const char *index_file, const char *prefix,
685703
struct commit *current_head,
686704
struct wt_status *s,
@@ -745,15 +763,33 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
745763
} else if (fixup_message) {
746764
struct pretty_print_context ctx = {0};
747765
struct commit *commit;
748-
commit = lookup_commit_reference_by_name(fixup_message);
766+
char *fmt;
767+
commit = lookup_commit_reference_by_name(fixup_commit);
749768
if (!commit)
750-
die(_("could not lookup commit %s"), fixup_message);
769+
die(_("could not lookup commit %s"), fixup_commit);
751770
ctx.output_encoding = get_commit_output_encoding();
752-
format_commit_message(commit, "fixup! %s\n\n",
753-
&sb, &ctx);
754-
if (have_option_m)
755-
strbuf_addbuf(&sb, &message);
771+
fmt = xstrfmt("%s! %%s\n\n", fixup_prefix);
772+
format_commit_message(commit, fmt, &sb, &ctx);
773+
free(fmt);
756774
hook_arg1 = "message";
775+
776+
/*
777+
* Only `-m` commit message option is checked here, as
778+
* it supports `--fixup` to append the commit message.
779+
*
780+
* The other commit message options `-c`/`-C`/`-F` are
781+
* incompatible with all the forms of `--fixup` and
782+
* have already errored out while parsing the `git commit`
783+
* options.
784+
*/
785+
if (have_option_m && !strcmp(fixup_prefix, "fixup"))
786+
strbuf_addbuf(&sb, &message);
787+
788+
if (!strcmp(fixup_prefix, "amend")) {
789+
if (have_option_m)
790+
die(_("cannot combine -m with --fixup:%s"), fixup_message);
791+
prepare_amend_commit(commit, &sb, &ctx);
792+
}
757793
} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {
758794
size_t merge_msg_start;
759795

@@ -1152,6 +1188,19 @@ static void finalize_deferred_config(struct wt_status *s)
11521188
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
11531189
}
11541190

1191+
static void check_fixup_reword_options(int argc, const char *argv[]) {
1192+
if (whence != FROM_COMMIT) {
1193+
if (whence == FROM_MERGE)
1194+
die(_("You are in the middle of a merge -- cannot reword."));
1195+
else if (is_from_cherry_pick(whence))
1196+
die(_("You are in the middle of a cherry-pick -- cannot reword."));
1197+
}
1198+
if (argc)
1199+
die(_("cannot combine reword option of --fixup with path '%s'"), *argv);
1200+
if (patch_interactive || interactive || all || also || only)
1201+
die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only"));
1202+
}
1203+
11551204
static int parse_and_validate_options(int argc, const char *argv[],
11561205
const struct option *options,
11571206
const char * const usage[],
@@ -1170,7 +1219,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
11701219
if (force_author && renew_authorship)
11711220
die(_("Using both --reset-author and --author does not make sense"));
11721221

1173-
if (logfile || have_option_m || use_message || fixup_message)
1222+
if (logfile || have_option_m || use_message)
11741223
use_editor = 0;
11751224
if (0 <= edit_flag)
11761225
use_editor = edit_flag;
@@ -1227,6 +1276,42 @@ static int parse_and_validate_options(int argc, const char *argv[],
12271276

12281277
if (also + only + all + interactive > 1)
12291278
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
1279+
1280+
if (fixup_message) {
1281+
/*
1282+
* We limit --fixup's suboptions to only alpha characters.
1283+
* If the first character after a run of alpha is colon,
1284+
* then the part before the colon may be a known suboption
1285+
* name like `amend` or `reword`, or a misspelt suboption
1286+
* name. In either case, we treat it as
1287+
* --fixup=<suboption>:<arg>.
1288+
*
1289+
* Otherwise, we are dealing with --fixup=<commit>.
1290+
*/
1291+
char *p = fixup_message;
1292+
while (isalpha(*p))
1293+
p++;
1294+
if (p > fixup_message && *p == ':') {
1295+
*p = '\0';
1296+
fixup_commit = p + 1;
1297+
if (!strcmp("amend", fixup_message) ||
1298+
!strcmp("reword", fixup_message)) {
1299+
fixup_prefix = "amend";
1300+
allow_empty = 1;
1301+
if (*fixup_message == 'r') {
1302+
check_fixup_reword_options(argc, argv);
1303+
only = 1;
1304+
}
1305+
} else {
1306+
die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit);
1307+
}
1308+
} else {
1309+
fixup_commit = fixup_message;
1310+
fixup_prefix = "fixup";
1311+
use_editor = 0;
1312+
}
1313+
}
1314+
12301315
cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
12311316

12321317
handle_untracked_files_arg(s);
@@ -1504,7 +1589,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
15041589
OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
15051590
OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
15061591
OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
1507-
OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
1592+
/*
1593+
* TRANSLATORS: Leave "[(amend|reword):]" as-is,
1594+
* and only translate <commit>.
1595+
*/
1596+
OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")),
15081597
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
15091598
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
15101599
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)
16631752
exit(1);
16641753
}
16651754

1755+
if (fixup_message && starts_with(sb.buf, "amend! ") &&
1756+
!allow_empty_message) {
1757+
struct strbuf body = STRBUF_INIT;
1758+
size_t len = commit_subject_length(sb.buf);
1759+
strbuf_addstr(&body, sb.buf + len);
1760+
if (message_is_empty(&body, cleanup_mode)) {
1761+
rollback_index_files();
1762+
fprintf(stderr, _("Aborting commit due to empty commit message body.\n"));
1763+
exit(1);
1764+
}
1765+
strbuf_release(&body);
1766+
}
1767+
16661768
if (amend) {
16671769
const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL };
16681770
extra = read_commit_extra_headers(current_head, exclude_gpgsig);

commit.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,20 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
535535
return eol - p;
536536
}
537537

538+
size_t commit_subject_length(const char *body)
539+
{
540+
const char *p = body;
541+
while (*p) {
542+
const char *next = skip_blank_lines(p);
543+
if (next != p)
544+
break;
545+
p = strchrnul(p, '\n');
546+
if (*p)
547+
p++;
548+
}
549+
return p - body;
550+
}
551+
538552
struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
539553
{
540554
struct commit_list *new_list = xmalloc(sizeof(struct commit_list));

commit.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ const void *detach_commit_buffer(struct commit *, unsigned long *sizep);
167167
/* Find beginning and length of commit subject. */
168168
int find_commit_subject(const char *commit_buffer, const char **subject);
169169

170+
/* Return length of the commit subject from commit log message. */
171+
size_t commit_subject_length(const char *body);
172+
170173
struct commit_list *commit_list_insert(struct commit *item,
171174
struct commit_list **list);
172175
int commit_list_contains(struct commit *item,

sequencer.c

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,20 +1732,6 @@ enum todo_item_flags {
17321732
TODO_EDIT_FIXUP_MSG = (1 << 2),
17331733
};
17341734

1735-
static size_t subject_length(const char *body)
1736-
{
1737-
const char *p = body;
1738-
while (*p) {
1739-
const char *next = skip_blank_lines(p);
1740-
if (next != p)
1741-
break;
1742-
p = strchrnul(p, '\n');
1743-
if (*p)
1744-
p++;
1745-
}
1746-
return p - body;
1747-
}
1748-
17491735
static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
17501736
static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
17511737
static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
@@ -1869,7 +1855,7 @@ static int append_squash_message(struct strbuf *buf, const char *body,
18691855
if (starts_with(body, "amend!") ||
18701856
((command == TODO_SQUASH || seen_squash(opts)) &&
18711857
(starts_with(body, "squash!") || starts_with(body, "fixup!"))))
1872-
commented_len = subject_length(body);
1858+
commented_len = commit_subject_length(body);
18731859

18741860
strbuf_addf(buf, "\n%c ", comment_line_char);
18751861
strbuf_addf(buf, _(nth_commit_msg_fmt),

t/t3437-rebase-fixup-options.sh

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -72,40 +72,16 @@ test_expect_success 'setup' '
7272
git commit --fixup=HEAD -a &&
7373
git tag B1 &&
7474
test_tick &&
75-
git commit --allow-empty -F - <<-EOF &&
76-
amend! B
77-
$EMPTY
78-
B
79-
$EMPTY
80-
edited 1
81-
EOF
75+
FAKE_COMMIT_AMEND="edited 1" git commit --fixup=reword:B &&
8276
test_tick &&
83-
git commit --allow-empty -F - <<-EOF &&
84-
amend! amend! B
85-
$EMPTY
86-
B
87-
$EMPTY
88-
edited 1
89-
$EMPTY
90-
edited 2
91-
EOF
77+
FAKE_COMMIT_AMEND="edited 2" git commit --fixup=reword:HEAD &&
9278
echo B2 >B &&
9379
test_tick &&
9480
FAKE_COMMIT_AMEND="edited squash" git commit --squash=HEAD -a &&
9581
git tag B2 &&
9682
echo B3 >B &&
9783
test_tick &&
98-
git commit -a -F - <<-EOF &&
99-
amend! amend! amend! B
100-
$EMPTY
101-
B
102-
$EMPTY
103-
edited 1
104-
$EMPTY
105-
edited 2
106-
$EMPTY
107-
edited 3
108-
EOF
84+
FAKE_COMMIT_AMEND="edited 3" git commit -a --fixup=amend:HEAD^ &&
10985
git tag B3 &&
11086
11187
GIT_AUTHOR_NAME="Rebase Author" &&

0 commit comments

Comments
 (0)