1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-09 19:36:10 +02:00

Merge branch 'cm/rebase-i' into next

"rebase -i" is getting cleaned up and also enhanced.

* cm/rebase-i:
  doc/git-rebase: add documentation for fixup [-C|-c] options
  rebase -i: teach --autosquash to work with amend!
  t3437: test script for fixup [-C|-c] options in interactive rebase
  rebase -i: add fixup [-C | -c] command
  sequencer: use const variable for commit message comments
  sequencer: pass todo_item to do_pick_commit()
  rebase -i: comment out squash!/fixup! subjects from squash message
  sequencer: factor out code to append squash message
  rebase -i: only write fixup-message when it's needed
This commit is contained in:
Junio C Hamano 2021-02-01 16:07:59 -08:00
commit 4f9aa6cec3
9 changed files with 587 additions and 61 deletions

View File

@ -887,9 +887,17 @@ If you want to fold two or more commits into one, replace the command
"pick" for the second and subsequent commits with "squash" or "fixup".
If the commits had different authors, the folded commit will be
attributed to the author of the first commit. The suggested commit
message for the folded commit is the concatenation of the commit
messages of the first commit and of those with the "squash" command,
but omits the commit messages of commits with the "fixup" command.
message for the folded commit is the concatenation of the first
commit's message with those identified by "squash" commands, omitting the
messages of commits identified by "fixup" commands, unless "fixup -c"
is used. In that case the suggested commit message is only the message
of the "fixup -c" commit, and an editor is opened allowing you to edit
the message. The contents (patch) of the "fixup -c" commit are still
incorporated into the folded commit. If there is more than one "fixup -c"
commit, the message from the last last one is used. You can also use
"fixup -C" to get the same behavior as "fixup -c" except without opening
an editor.
'git rebase' will stop when "pick" has been replaced with "edit" or
when a command fails due to merge errors. When you are done editing

View File

@ -44,7 +44,9 @@ void append_todo_help(int command_count,
"r, reword <commit> = use commit, but edit the commit message\n"
"e, edit <commit> = use commit, but stop for amending\n"
"s, squash <commit> = use commit, but meld into previous commit\n"
"f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
"f, fixup [-C | -c] <commit> = like \"squash\", but discard this\n"
" commit's log message. Use -C to replace with this\n"
" commit message or -c to edit the commit message\n"
"x, exec <command> = run command (the rest of the line) using shell\n"
"b, break = stop here (continue rebase later with 'git rebase --continue')\n"
"d, drop <commit> = remove commit\n"

View File

@ -1726,13 +1726,198 @@ static int is_pick_or_similar(enum todo_command command)
}
}
enum todo_item_flags {
TODO_EDIT_MERGE_MSG = (1 << 0),
TODO_REPLACE_FIXUP_MSG = (1 << 1),
TODO_EDIT_FIXUP_MSG = (1 << 2),
};
static size_t subject_length(const char *body)
{
const char *p = body;
while (*p) {
const char *next = skip_blank_lines(p);
if (next != p)
break;
p = strchrnul(p, '\n');
if (*p)
p++;
}
return p - body;
}
static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
static int check_fixup_flag(enum todo_command command,
enum todo_item_flags flag)
{
return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
(flag & TODO_EDIT_FIXUP_MSG));
}
/*
* Wrapper around strbuf_add_commented_lines() which avoids double
* commenting commit subjects.
*/
static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
{
const char *s = str;
while (len > 0 && s[0] == comment_line_char) {
size_t count;
const char *n = memchr(s, '\n', len);
if (!n)
count = len;
else
count = n - s + 1;
strbuf_add(buf, s, count);
s += count;
len -= count;
}
strbuf_add_commented_lines(buf, s, len);
}
/* Does the current fixup chain contain a squash command? */
static int seen_squash(struct replay_opts *opts)
{
return starts_with(opts->current_fixups.buf, "squash") ||
strstr(opts->current_fixups.buf, "\nsquash");
}
static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
{
strbuf_setlen(buf1, 2);
strbuf_addf(buf1, _(nth_commit_msg_fmt), n);
strbuf_addch(buf1, '\n');
strbuf_setlen(buf2, 2);
strbuf_addf(buf2, _(skip_nth_commit_msg_fmt), n);
strbuf_addch(buf2, '\n');
}
/*
* Comment out any un-commented commit messages, updating the message comments
* to say they will be skipped but do not comment out the empty lines that
* surround commit messages and their comments.
*/
static void update_squash_message_for_fixup(struct strbuf *msg)
{
void (*copy_lines)(struct strbuf *, const void *, size_t) = strbuf_add;
struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
const char *s, *start;
char *orig_msg;
size_t orig_msg_len;
int i = 1;
strbuf_addf(&buf1, "# %s\n", _(first_commit_msg_str));
strbuf_addf(&buf2, "# %s\n", _(skip_first_commit_msg_str));
s = start = orig_msg = strbuf_detach(msg, &orig_msg_len);
while (s) {
const char *next;
size_t off;
if (skip_prefix(s, buf1.buf, &next)) {
/*
* Copy the last message, preserving the blank line
* preceding the current line
*/
off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
copy_lines(msg, start, s - start - off);
if (off)
strbuf_addch(msg, '\n');
/*
* The next message needs to be commented out but the
* message header is already commented out so just copy
* it and the blank line that follows it.
*/
strbuf_addbuf(msg, &buf2);
if (*next == '\n')
strbuf_addch(msg, *next++);
start = s = next;
copy_lines = add_commented_lines;
update_comment_bufs(&buf1, &buf2, ++i);
} else if (skip_prefix(s, buf2.buf, &next)) {
off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
copy_lines(msg, start, s - start - off);
start = s - off;
s = next;
copy_lines = strbuf_add;
update_comment_bufs(&buf1, &buf2, ++i);
} else {
s = strchr(s, '\n');
if (s)
s++;
}
}
copy_lines(msg, start, orig_msg_len - (start - orig_msg));
free(orig_msg);
strbuf_release(&buf1);
strbuf_release(&buf2);
}
static int append_squash_message(struct strbuf *buf, const char *body,
enum todo_command command, struct replay_opts *opts,
enum todo_item_flags flag)
{
const char *fixup_msg;
size_t commented_len = 0, fixup_off;
/*
* amend is non-interactive and not normally used with fixup!
* or squash! commits, so only comment out those subjects when
* squashing commit messages.
*/
if (starts_with(body, "amend!") ||
((command == TODO_SQUASH || seen_squash(opts)) &&
(starts_with(body, "squash!") || starts_with(body, "fixup!"))))
commented_len = subject_length(body);
strbuf_addf(buf, "\n%c ", comment_line_char);
strbuf_addf(buf, _(nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(buf, "\n\n");
strbuf_add_commented_lines(buf, body, commented_len);
/* buf->buf may be reallocated so store an offset into the buffer */
fixup_off = buf->len;
strbuf_addstr(buf, body + commented_len);
/* fixup -C after squash behaves like squash */
if (check_fixup_flag(command, flag) && !seen_squash(opts)) {
/*
* We're replacing the commit message so we need to
* append the Signed-off-by: trailer if the user
* requested '--signoff'.
*/
if (opts->signoff)
append_signoff(buf, 0, 0);
if ((command == TODO_FIXUP) &&
(flag & TODO_REPLACE_FIXUP_MSG) &&
(file_exists(rebase_path_fixup_msg()) ||
!file_exists(rebase_path_squash_msg()))) {
fixup_msg = skip_blank_lines(buf->buf + fixup_off);
if (write_message(fixup_msg, strlen(fixup_msg),
rebase_path_fixup_msg(), 0) < 0)
return error(_("cannot write '%s'"),
rebase_path_fixup_msg());
} else {
unlink(rebase_path_fixup_msg());
}
} else {
unlink(rebase_path_fixup_msg());
}
return 0;
}
static int update_squash_messages(struct repository *r,
enum todo_command command,
struct commit *commit,
struct replay_opts *opts)
struct replay_opts *opts,
enum todo_item_flags flag)
{
struct strbuf buf = STRBUF_INIT;
int res;
int res = 0;
const char *message, *body;
const char *encoding = get_commit_output_encoding();
@ -1748,10 +1933,12 @@ static int update_squash_messages(struct repository *r,
buf.buf : strchrnul(buf.buf, '\n');
strbuf_addf(&header, "%c ", comment_line_char);
strbuf_addf(&header, _("This is a combination of %d commits."),
strbuf_addf(&header, _(combined_commit_msg_fmt),
opts->current_fixup_count + 2);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
strbuf_release(&header);
if (check_fixup_flag(command, flag) && !seen_squash(opts))
update_squash_message_for_fixup(&buf);
} else {
struct object_id head;
struct commit *head_commit;
@ -1765,19 +1952,22 @@ static int update_squash_messages(struct repository *r,
return error(_("could not read HEAD's commit message"));
find_commit_subject(head_message, &body);
if (write_message(body, strlen(body),
rebase_path_fixup_msg(), 0)) {
if (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
rebase_path_fixup_msg(), 0) < 0) {
unuse_commit_buffer(head_commit, head_message);
return error(_("cannot write '%s'"),
rebase_path_fixup_msg());
return error(_("cannot write '%s'"), rebase_path_fixup_msg());
}
strbuf_addf(&buf, "%c ", comment_line_char);
strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addstr(&buf, _("This is the 1st commit message:"));
strbuf_addstr(&buf, check_fixup_flag(command, flag) ?
_(skip_first_commit_msg_str) :
_(first_commit_msg_str));
strbuf_addstr(&buf, "\n\n");
strbuf_addstr(&buf, body);
if (check_fixup_flag(command, flag))
strbuf_add_commented_lines(&buf, body, strlen(body));
else
strbuf_addstr(&buf, body);
unuse_commit_buffer(head_commit, head_message);
}
@ -1787,16 +1977,11 @@ static int update_squash_messages(struct repository *r,
oid_to_hex(&commit->object.oid));
find_commit_subject(message, &body);
if (command == TODO_SQUASH) {
unlink(rebase_path_fixup_msg());
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addf(&buf, _("This is the commit message #%d:"),
++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
strbuf_addstr(&buf, body);
if (command == TODO_SQUASH || check_fixup_flag(command, flag)) {
res = append_squash_message(&buf, body, command, opts, flag);
} else if (command == TODO_FIXUP) {
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body));
@ -1804,7 +1989,9 @@ static int update_squash_messages(struct repository *r,
return error(_("unknown command: %d"), command);
unuse_commit_buffer(commit, message);
res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
if (!res)
res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
0);
strbuf_release(&buf);
if (!res) {
@ -1861,8 +2048,7 @@ static void record_in_rewritten(struct object_id *oid,
}
static int do_pick_commit(struct repository *r,
enum todo_command command,
struct commit *commit,
struct todo_item *item,
struct replay_opts *opts,
int final_fixup, int *check_todo)
{
@ -1875,6 +2061,8 @@ static int do_pick_commit(struct repository *r,
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
int res, unborn = 0, reword = 0, allow, drop_commit;
enum todo_command command = item->command;
struct commit *commit = item->commit;
if (opts->no_commit) {
/*
@ -2004,7 +2192,8 @@ static int do_pick_commit(struct repository *r,
if (command == TODO_REWORD)
reword = 1;
else if (is_fixup(command)) {
if (update_squash_messages(r, command, commit, opts))
if (update_squash_messages(r, command, commit,
opts, item->flags))
return -1;
flags |= AMEND_MSG;
if (!final_fixup)
@ -2169,10 +2358,6 @@ static int read_and_refresh_cache(struct repository *r,
return 0;
}
enum todo_item_flags {
TODO_EDIT_MERGE_MSG = 1
};
void todo_list_release(struct todo_list *todo_list)
{
strbuf_release(&todo_list->buf);
@ -2259,6 +2444,18 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
return 0;
}
if (item->command == TODO_FIXUP) {
if (skip_prefix(bol, "-C", &bol) &&
(*bol == ' ' || *bol == '\t')) {
bol += strspn(bol, " \t");
item->flags |= TODO_REPLACE_FIXUP_MSG;
} else if (skip_prefix(bol, "-c", &bol) &&
(*bol == ' ' || *bol == '\t')) {
bol += strspn(bol, " \t");
item->flags |= TODO_EDIT_FIXUP_MSG;
}
}
if (item->command == TODO_MERGE) {
if (skip_prefix(bol, "-C", &bol))
bol += strspn(bol, " \t");
@ -4124,8 +4321,8 @@ static int pick_commits(struct repository *r,
setenv(GIT_REFLOG_ACTION, reflog_message(opts,
command_to_string(item->command), NULL),
1);
res = do_pick_commit(r, item->command, item->commit,
opts, is_final_fixup(todo_list),
res = do_pick_commit(r, item, opts,
is_final_fixup(todo_list),
&check_todo);
if (is_rebase_i(opts))
setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
@ -4587,11 +4784,14 @@ static int single_pick(struct repository *r,
struct replay_opts *opts)
{
int check_todo;
struct todo_item item;
item.command = opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT;
item.commit = cmit;
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
return do_pick_commit(r, opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT, cmit, opts, 0,
&check_todo);
return do_pick_commit(r, &item, opts, 0, &check_todo);
}
int sequencer_pick_revisions(struct repository *r,
@ -5262,6 +5462,14 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
short_commit_name(item->commit) :
oid_to_hex(&item->commit->object.oid);
if (item->command == TODO_FIXUP) {
if (item->flags & TODO_EDIT_FIXUP_MSG)
strbuf_addstr(buf, " -c");
else if (item->flags & TODO_REPLACE_FIXUP_MSG) {
strbuf_addstr(buf, " -C");
}
}
if (item->command == TODO_MERGE) {
if (item->flags & TODO_EDIT_MERGE_MSG)
strbuf_addstr(buf, " -c");
@ -5462,6 +5670,12 @@ static int subject2item_cmp(const void *fndata,
define_commit_slab(commit_todo_item, struct todo_item *);
static inline int skip_fixup_amend_squash(const char *subject, const char **p) {
return skip_prefix(subject, "fixup! ", p) ||
skip_prefix(subject, "amend! ", p) ||
skip_prefix(subject, "squash! ", p);
}
/*
* Rearrange the todo list that has both "pick commit-id msg" and "pick
* commit-id fixup!/squash! msg" in it so that the latter is put immediately
@ -5520,15 +5734,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
format_subject(&buf, subject, " ");
subject = subjects[i] = strbuf_detach(&buf, &subject_len);
unuse_commit_buffer(item->commit, commit_buffer);
if ((skip_prefix(subject, "fixup! ", &p) ||
skip_prefix(subject, "squash! ", &p))) {
if (skip_fixup_amend_squash(subject, &p)) {
struct commit *commit2;
for (;;) {
while (isspace(*p))
p++;
if (!skip_prefix(p, "fixup! ", &p) &&
!skip_prefix(p, "squash! ", &p))
if (!skip_fixup_amend_squash(p, &p))
break;
}
@ -5558,9 +5770,14 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
}
if (i2 >= 0) {
rearranged = 1;
todo_list->items[i].command =
starts_with(subject, "fixup!") ?
TODO_FIXUP : TODO_SQUASH;
if (starts_with(subject, "fixup!")) {
todo_list->items[i].command = TODO_FIXUP;
} else if (starts_with(subject, "amend!")) {
todo_list->items[i].command = TODO_FIXUP;
todo_list->items[i].flags = TODO_REPLACE_FIXUP_MSG;
} else {
todo_list->items[i].command = TODO_SQUASH;
}
if (tail[i2] < 0) {
next[i] = next[i2];
next[i2] = i;

View File

@ -4,6 +4,7 @@
#
# - override the commit message with $FAKE_COMMIT_MESSAGE
# - amend the commit message with $FAKE_COMMIT_AMEND
# - copy the original commit message to a file with $FAKE_MESSAGE_COPY
# - check that non-commit messages have a certain line count with $EXPECT_COUNT
# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
# - rewrite a rebase -i script as directed by $FAKE_LINES.
@ -32,6 +33,7 @@ set_fake_editor () {
exit
test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
test -z "$FAKE_MESSAGE_COPY" || cat "$1" >"$FAKE_MESSAGE_COPY"
exit
;;
esac
@ -50,6 +52,8 @@ set_fake_editor () {
action="$line";;
exec_*|x_*|break|b)
echo "$line" | sed 's/_/ /g' >> "$1";;
merge_*|fixup_*)
action=$(echo "$line" | sed 's/_/ /g');;
"#")
echo '# comment' >> "$1";;
">")

View File

@ -84,8 +84,7 @@ test_auto_squash () {
echo 1 >file1 &&
git add -u &&
test_tick &&
git commit -m "squash! first" &&
git commit -m "squash! first" -m "extra para for first" &&
git tag $1 &&
test_tick &&
git rebase $2 -i HEAD^^^ &&
@ -142,7 +141,7 @@ test_expect_success 'auto squash that matches 2 commits' '
echo 1 >file1 &&
git add -u &&
test_tick &&
git commit -m "squash! first" &&
git commit -m "squash! first" -m "extra para for first" &&
git tag final-multisquash &&
test_tick &&
git rebase --autosquash -i HEAD~4 &&
@ -195,7 +194,7 @@ test_expect_success 'auto squash that matches a sha1' '
git add -u &&
test_tick &&
oid=$(git rev-parse --short HEAD^) &&
git commit -m "squash! $oid" &&
git commit -m "squash! $oid" -m "extra para" &&
git tag final-shasquash &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@ -206,7 +205,8 @@ test_expect_success 'auto squash that matches a sha1' '
git cat-file blob HEAD^:file1 >actual &&
test_cmp expect actual &&
git cat-file commit HEAD^ >commit &&
grep squash commit >actual &&
! grep "squash" commit &&
grep "^extra para" commit >actual &&
test_line_count = 1 actual
'
@ -216,7 +216,7 @@ test_expect_success 'auto squash that matches longer sha1' '
git add -u &&
test_tick &&
oid=$(git rev-parse --short=11 HEAD^) &&
git commit -m "squash! $oid" &&
git commit -m "squash! $oid" -m "extra para" &&
git tag final-longshasquash &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@ -227,7 +227,8 @@ test_expect_success 'auto squash that matches longer sha1' '
git cat-file blob HEAD^:file1 >actual &&
test_cmp expect actual &&
git cat-file commit HEAD^ >commit &&
grep squash commit >actual &&
! grep "squash" commit &&
grep "^extra para" commit >actual &&
test_line_count = 1 actual
'
@ -236,7 +237,7 @@ test_auto_commit_flags () {
echo 1 >file1 &&
git add -u &&
test_tick &&
git commit --$1 first-commit &&
git commit --$1 first-commit -m "extra para for first" &&
git tag final-commit-$1 &&
test_tick &&
git rebase --autosquash -i HEAD^^^ &&
@ -264,11 +265,11 @@ test_auto_fixup_fixup () {
echo 1 >file1 &&
git add -u &&
test_tick &&
git commit -m "$1! first" &&
git commit -m "$1! first" -m "extra para for first" &&
echo 2 >file1 &&
git add -u &&
test_tick &&
git commit -m "$1! $2! first" &&
git commit -m "$1! $2! first" -m "second extra para for first" &&
git tag "final-$1-$2" &&
test_tick &&
(
@ -329,12 +330,12 @@ test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' '
git add -u &&
test_tick &&
oid=$(git rev-parse --short HEAD^) &&
git commit -m "squash! $oid" &&
git commit -m "squash! $oid" -m "extra para for first" &&
echo 1 >file1 &&
git add -u &&
test_tick &&
subject=$(git log -n 1 --format=%s HEAD~2) &&
git commit -m "squash! $subject" &&
git commit -m "squash! $subject" -m "second extra para for first" &&
git tag final-squash-instFmt &&
test_tick &&
git rebase --autosquash -i HEAD~4 &&
@ -345,8 +346,9 @@ test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' '
git cat-file blob HEAD^:file1 >actual &&
test_cmp expect actual &&
git cat-file commit HEAD^ >commit &&
grep squash commit >actual &&
test_line_count = 2 actual
! grep "squash" commit &&
grep first commit >actual &&
test_line_count = 3 actual
'
test_expect_success 'autosquash with empty custom instructionFormat' '

225
t/t3437-rebase-fixup-options.sh Executable file
View File

@ -0,0 +1,225 @@
#!/bin/sh
#
# Copyright (c) 2018 Phillip Wood
#
test_description='git rebase interactive fixup options
This test checks the "fixup [-C|-c]" command of rebase interactive.
In addition to amending the contents of the commit, "fixup -C"
replaces the original commit message with the message of the fixup
commit. "fixup -c" also replaces the original message, but opens the
editor to allow the user to edit the message before committing.
'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
EMPTY=""
test_commit_message () {
rev="$1" && # commit or tag we want to test
file="$2" && # test against the content of a file
git show --no-patch --pretty=format:%B "$rev" >actual-message &&
if test "$2" = -m
then
str="$3" && # test against a string
printf "%s\n" "$str" >tmp-expected-message &&
file="tmp-expected-message"
fi
test_cmp "$file" actual-message
}
get_author () {
rev="$1" &&
git log -1 --pretty=format:"%an %ae" "$rev"
}
test_expect_success 'setup' '
cat >message <<-EOF &&
amend! B
${EMPTY}
new subject
${EMPTY}
new
body
EOF
sed "1,2d" message >expected-message &&
test_commit A A &&
test_commit B B &&
get_author HEAD >expected-author &&
ORIG_AUTHOR_NAME="$GIT_AUTHOR_NAME" &&
ORIG_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" &&
GIT_AUTHOR_NAME="Amend Author" &&
GIT_AUTHOR_EMAIL="amend@example.com" &&
test_commit "$(cat message)" A A1 A1 &&
test_commit A2 A &&
test_commit A3 A &&
GIT_AUTHOR_NAME="$ORIG_AUTHOR_NAME" &&
GIT_AUTHOR_EMAIL="$ORIG_AUTHOR_EMAIL" &&
git checkout -b conflicts-branch A &&
test_commit conflicts A &&
set_fake_editor &&
git checkout -b branch B &&
echo B1 >B &&
test_tick &&
git commit --fixup=HEAD -a &&
test_tick &&
git commit --allow-empty -F - <<-EOF &&
amend! B
${EMPTY}
B
${EMPTY}
edited 1
EOF
test_tick &&
git commit --allow-empty -F - <<-EOF &&
amend! amend! B
${EMPTY}
B
${EMPTY}
edited 1
${EMPTY}
edited 2
EOF
echo B2 >B &&
test_tick &&
FAKE_COMMIT_AMEND="edited squash" git commit --squash=HEAD -a &&
echo B3 >B &&
test_tick &&
git commit -a -F - <<-EOF &&
amend! amend! amend! B
${EMPTY}
B
${EMPTY}
edited 1
${EMPTY}
edited 2
${EMPTY}
edited 3
EOF
GIT_AUTHOR_NAME="Rebase Author" &&
GIT_AUTHOR_EMAIL="rebase.author@example.com" &&
GIT_COMMITTER_NAME="Rebase Committer" &&
GIT_COMMITTER_EMAIL="rebase.committer@example.com"
'
test_expect_success 'simple fixup -C works' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A2 &&
FAKE_LINES="1 fixup_-C 2" git rebase -i B &&
test_cmp_rev HEAD^ B &&
test_cmp_rev HEAD^{tree} A2^{tree} &&
test_commit_message HEAD -m "A2"
'
test_expect_success 'simple fixup -c works' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A2 &&
git log -1 --pretty=format:%B >expected-fixup-message &&
test_write_lines "" "Modified A2" >>expected-fixup-message &&
FAKE_LINES="1 fixup_-c 2" \
FAKE_COMMIT_AMEND="Modified A2" \
git rebase -i B &&
test_cmp_rev HEAD^ B &&
test_cmp_rev HEAD^{tree} A2^{tree} &&
test_commit_message HEAD expected-fixup-message
'
test_expect_success 'fixup -C removes amend! from message' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A1 &&
FAKE_LINES="1 fixup_-C 2" git rebase -i A &&
test_cmp_rev HEAD^ A &&
test_cmp_rev HEAD^{tree} A1^{tree} &&
test_commit_message HEAD expected-message &&
get_author HEAD >actual-author &&
test_cmp expected-author actual-author
'
test_expect_success 'fixup -C with conflicts gives correct message' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A1 &&
test_must_fail env FAKE_LINES="1 fixup_-C 2" git rebase -i conflicts &&
git checkout --theirs -- A &&
git add A &&
FAKE_COMMIT_AMEND=edited git rebase --continue &&
test_cmp_rev HEAD^ conflicts &&
test_cmp_rev HEAD^{tree} A1^{tree} &&
test_write_lines "" edited >>expected-message &&
test_commit_message HEAD expected-message &&
get_author HEAD >actual-author &&
test_cmp expected-author actual-author
'
test_expect_success 'skipping fixup -C after fixup gives correct message' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A3 &&
test_must_fail env FAKE_LINES="1 fixup 2 fixup_-C 4" git rebase -i A &&
git reset --hard &&
FAKE_COMMIT_AMEND=edited git rebase --continue &&
test_commit_message HEAD -m "B"
'
test_expect_success 'sequence of fixup, fixup -C & squash --signoff works' '
git checkout --detach branch &&
FAKE_LINES="1 fixup 2 fixup_-C 3 fixup_-C 4 squash 5 fixup_-C 6" \
FAKE_COMMIT_AMEND=squashed \
FAKE_MESSAGE_COPY=actual-squash-message \
git -c commit.status=false rebase -ik --signoff A &&
git diff-tree --exit-code --patch HEAD branch -- &&
test_cmp_rev HEAD^ A &&
test_i18ncmp "$TEST_DIRECTORY/t3437/expected-squash-message" \
actual-squash-message
'
test_expect_success 'first fixup -C commented out in sequence fixup fixup -C fixup -C' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout branch && git checkout --detach branch~2 &&
git log -1 --pretty=format:%b >expected-message &&
FAKE_LINES="1 fixup 2 fixup_-C 3 fixup_-C 4" git rebase -i A &&
test_cmp_rev HEAD^ A &&
test_commit_message HEAD expected-message
'
test_expect_success 'multiple fixup -c opens editor once' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A3 &&
base=$(git rev-parse HEAD~4) &&
FAKE_COMMIT_MESSAGE="Modified-A3" \
FAKE_LINES="1 fixup_-C 2 fixup_-c 3 fixup_-c 4" \
EXPECT_HEADER_COUNT=4 \
git rebase -i $base &&
test_cmp_rev $base HEAD^ &&
test 1 = $(git show | grep Modified-A3 | wc -l)
'
test_expect_success 'sequence squash, fixup & fixup -c gives combined message' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout --detach A3 &&
FAKE_LINES="1 squash 2 fixup 3 fixup_-c 4" \
FAKE_MESSAGE_COPY=actual-combined-message \
git -c commit.status=false rebase -i A &&
test_i18ncmp "$TEST_DIRECTORY/t3437/expected-combined-message" \
actual-combined-message &&
test_cmp_rev HEAD^ A
'
test_expect_success 'fixup -C works upon --autosquash with amend!' '
git checkout --detach branch &&
FAKE_COMMIT_AMEND=squashed \
FAKE_MESSAGE_COPY=actual-squash-message \
git -c commit.status=false rebase -ik --autosquash \
--signoff A &&
git diff-tree --exit-code --patch HEAD branch -- &&
test_cmp_rev HEAD^ A &&
test_i18ncmp "$TEST_DIRECTORY/t3437/expected-squash-message" \
actual-squash-message
'
test_done

View File

@ -0,0 +1,21 @@
# This is a combination of 4 commits.
# This is the 1st commit message:
B
# This is the commit message #2:
# amend! B
new subject
new
body
# The commit message #3 will be skipped:
# A2
# This is the commit message #4:
A3

View File

@ -0,0 +1,51 @@
# This is a combination of 6 commits.
# The 1st commit message will be skipped:
# B
#
# Signed-off-by: Rebase Committer <rebase.committer@example.com>
# The commit message #2 will be skipped:
# fixup! B
# The commit message #3 will be skipped:
# amend! B
#
# B
#
# edited 1
#
# Signed-off-by: Rebase Committer <rebase.committer@example.com>
# This is the commit message #4:
# amend! amend! B
B
edited 1
edited 2
Signed-off-by: Rebase Committer <rebase.committer@example.com>
# This is the commit message #5:
# squash! amend! amend! B
edited squash
# This is the commit message #6:
# amend! amend! amend! B
B
edited 1
edited 2
edited 3
squashed

View File

@ -226,10 +226,6 @@ test_commit_autosquash_multi_encoding () {
git rev-list HEAD >actual &&
test_line_count = 3 actual &&
iconv -f $old -t UTF-8 "$TEST_DIRECTORY"/t3900/$msg >expect &&
if test $flag = squash; then
subject="$(head -1 expect)" &&
printf "\nsquash! %s\n" "$subject" >>expect
fi &&
git cat-file commit HEAD^ >raw &&
(sed "1,/^$/d" raw | iconv -f $new -t utf-8) >actual &&
test_cmp expect actual