Skip to content

Commit

Permalink
rebase -r: make 'merge -c' behave like reword
Browse files Browse the repository at this point in the history
If the user runs git log while rewording a commit it is confusing if
sometimes we're amending the commit that's being reworded and at other
times we're creating a new commit depending on whether we could
fast-forward or not[1]. For this reason the reword command ensures
that there are no uncommitted changes when rewording. The reword
command also allows the user to edit the todo list while the rebase is
paused. As 'merge -c' also rewords commits make it behave like reword
and add a test.

[1] https://lore.kernel.org/git/xmqqlfvu4be3.fsf@gitster-ct.c.googlers.com/T/#m133009cb91cf0917bcf667300f061178be56680a

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
phillipwood authored and gitster committed Aug 20, 2021
1 parent 5d213e4 commit 2be6b6f
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 19 deletions.
24 changes: 14 additions & 10 deletions sequencer.c
Expand Up @@ -3739,10 +3739,9 @@ static struct commit *lookup_label(const char *label, int len,
static int do_merge(struct repository *r,
struct commit *commit,
const char *arg, int arg_len,
int flags, struct replay_opts *opts)
int flags, int *check_todo, struct replay_opts *opts)
{
int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ?
EDIT_MSG | VERIFY_MSG : 0;
int run_commit_flags = 0;
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
Expand Down Expand Up @@ -3898,10 +3897,9 @@ static int do_merge(struct repository *r,
rollback_lock_file(&lock);
ret = fast_forward_to(r, &commit->object.oid,
&head_commit->object.oid, 0, opts);
if (flags & TODO_EDIT_MERGE_MSG) {
run_commit_flags |= AMEND_MSG;
if (flags & TODO_EDIT_MERGE_MSG)
goto fast_forward_edit;
}

goto leave_merge;
}

Expand Down Expand Up @@ -4035,10 +4033,17 @@ static int do_merge(struct repository *r,
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
fast_forward_edit:
ret = !!run_git_commit(git_path_merge_msg(r), opts,
run_commit_flags);

if (!ret && flags & TODO_EDIT_MERGE_MSG) {
fast_forward_edit:
*check_todo = 1;
run_commit_flags |= AMEND_MSG | EDIT_MSG | VERIFY_MSG;
ret = !!run_git_commit(NULL, opts, run_commit_flags);
}


leave_merge:
strbuf_release(&ref_name);
rollback_lock_file(&lock);
Expand Down Expand Up @@ -4405,9 +4410,8 @@ static int pick_commits(struct repository *r,
if ((res = do_reset(r, arg, item->arg_len, opts)))
reschedule = 1;
} else if (item->command == TODO_MERGE) {
if ((res = do_merge(r, item->commit,
arg, item->arg_len,
item->flags, opts)) < 0)
if ((res = do_merge(r, item->commit, arg, item->arg_len,
item->flags, &check_todo, opts)) < 0)
reschedule = 1;
else if (item->commit)
record_in_rewritten(&item->commit->object.oid,
Expand Down
50 changes: 50 additions & 0 deletions t/lib-rebase.sh
Expand Up @@ -151,3 +151,53 @@ test_editor_unchanged () {
EOF
test_cmp expect actual
}

# Set up an editor for testing reword commands
# Checks that there are no uncommitted changes when rewording and that the
# todo-list is reread after each
set_reword_editor () {
>reword-actual &&
>reword-oid &&

# Check rewording keeps the original authorship
GIT_AUTHOR_NAME="Reword Author"
GIT_AUTHOR_EMAIL="reword.author@example.com"
GIT_AUTHOR_DATE=@123456

write_script reword-sequence-editor.sh <<-\EOF &&
todo="$(cat "$1")" &&
echo "exec git log -1 --pretty=format:'%an <%ae> %at%n%B%n' \
>>reword-actual" >"$1" &&
printf "%s\n" "$todo" >>"$1"
EOF

write_script reword-editor.sh <<-EOF &&
# Save the oid of the first reworded commit so we can check rebase
# fast-forwards to it
if ! test -s reword-oid
then
git rev-parse HEAD >reword-oid
fi &&
# There should be no uncommited changes
git diff --exit-code HEAD &&
# The todo-list should be re-read after a reword
GIT_SEQUENCE_EDITOR="\"$PWD/reword-sequence-editor.sh\"" \
git rebase --edit-todo &&
echo edited >>"\$1"
EOF

test_set_editor "$PWD/reword-editor.sh"
}

# Check the results of a rebase after calling set_reword_editor
# Pass the commits that were reworded in the order that they were picked
# Expects the first pick to be a fast-forward
check_reworded_commits () {
test_cmp_rev "$(cat reword-oid)" "$1^{commit}" &&
git log --format="%an <%ae> %at%n%B%nedited%n" --no-walk=unsorted "$@" \
>reword-expected &&
test_cmp reword-expected reword-actual &&
git log --format="%an <%ae> %at%n%B" -n $# --first-parent --reverse \
>reword-log &&
test_cmp reword-expected reword-log
}
20 changes: 11 additions & 9 deletions t/t3430-rebase-merges.sh
Expand Up @@ -172,17 +172,19 @@ test_expect_success 'failed `merge <branch>` does not crash' '
grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
'

test_expect_success 'fast-forward merge -c still rewords' '
git checkout -b fast-forward-merge-c H &&
test_expect_success 'merge -c commits before rewording and reloads todo-list' '
cat >script-from-scratch <<-\EOF &&
merge -c E B
merge -c H G
EOF
git checkout -b merge-c H &&
(
set_fake_editor &&
FAKE_COMMIT_MESSAGE=edited \
GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
git rebase -ir @^
set_reword_editor &&
GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
git rebase -i -r D
) &&
echo edited >expected &&
git log --pretty=format:%B -1 >actual &&
test_cmp expected actual
check_reworded_commits E H
'

test_expect_success 'with a branch tip that was cherry-picked already' '
Expand Down

0 comments on commit 2be6b6f

Please sign in to comment.