Merge branch 'pb/status-rebase-fixes' into jch

A few fixes around "git status" while "git rebase" is running,
plus a corner case bug fix for "git rebase -r".

* pb/status-rebase-fixes:
  wt-status: suggest 'git rebase --continue' to conclude 'merge' instruction
  wt-status: also abbreviate 'merge' and 'fixup -C' lines during rebase
  SQUASH??? - <CAPig+cS92W_gYuNsaTvQxiP3xBK7Wpg0__uVkgAU1x0OFJUZgQ@mail.gmail.com>
  rebase -r: do create merge commit after empty resolution
Junio C Hamano 2025-05-01 14:07:18 -07:00
commit ec902f4769
5 changed files with 137 additions and 13 deletions

View File

@ -4333,6 +4333,7 @@ static int do_merge(struct repository *r,
error(_("could not even attempt to merge '%.*s'"),
merge_arg_len, arg);
unlink(git_path_merge_msg(r));
unlink(git_path_merge_head(r));
goto leave_merge;
}
/*
@ -5348,7 +5349,7 @@ static int commit_staged_changes(struct repository *r,
flags |= AMEND_MSG;
}

if (is_clean) {
if (is_clean && !file_exists(git_path_merge_head(r))) {
if (refs_ref_exists(get_main_ref_store(r),
"CHERRY_PICK_HEAD") &&
refs_delete_ref(get_main_ref_store(r), "",

View File

@ -111,6 +111,30 @@ test_expect_success 'rebase -r passes merge strategy options correctly' '
git rebase --continue
'

test_expect_success '--continue creates merge commit after empty resolution' '
git reset --hard main &&
git checkout -b rebase_i_merge &&
test_commit unrelated &&
git checkout -b rebase_i_merge_side &&
test_commit side2 main.txt &&
git checkout rebase_i_merge &&
test_commit side1 main.txt &&
PICK=$(git rev-parse --short rebase_i_merge) &&
test_must_fail git merge rebase_i_merge_side &&
echo side1 >main.txt &&
git add main.txt &&
test_tick &&
git commit --no-edit &&

test_must_fail env FAKE_LINES="1 2 3 5 6 7 8 9 10 11" \
git rebase -ir main &&
echo side1 >main.txt &&
git add main.txt &&
git rebase --continue &&
git log --merges >out &&
test_grep "Merge branch .rebase_i_merge_side." out
'

test_expect_success '--skip after failed fixup cleans commit message' '
test_when_finished "test_might_fail git rebase --abort" &&
git checkout -b with-conflicting-fixup &&

View File

@ -183,6 +183,81 @@ EOF
test_cmp expected actual
'

test_expect_success 'status during rebase -ir after conflicted merge (exec git merge)' '
git reset --hard main &&
git checkout -b rebase_i_merge &&
test_commit unrelated &&
git checkout -b rebase_i_merge_side &&
test_commit side2 main.txt &&
git checkout rebase_i_merge &&
test_commit side1 main.txt &&
PICK=$(git rev-parse --short rebase_i_merge) &&
test_must_fail git merge rebase_i_merge_side &&
echo side1 >main.txt &&
git add main.txt &&
test_tick &&
git commit --no-edit &&
MERGE=$(git rev-parse --short rebase_i_merge) &&
ONTO=$(git rev-parse --short main) &&
test_when_finished "git rebase --abort" &&
FAKE_LINES="1 2 3 5 6 7 8 9 10 exec_git_merge_refs/rewritten/rebase-i-merge-side" &&
export FAKE_LINES &&
test_must_fail git rebase -ir main &&
cat >expect <<EOF &&
interactive rebase in progress; onto $ONTO
Last commands done (8 commands done):
pick $PICK side1
exec git merge refs/rewritten/rebase-i-merge-side
(see more in file .git/rebase-merge/done)
No commands remaining.

You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)

Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: main.txt

no changes added to commit (use "git add" and/or "git commit -a")
EOF
git status --untracked-files=no >actual &&
test_cmp expect actual
'

test_expect_success 'status during rebase -ir after replaying conflicted merge (merge)' '
PICK=$(git rev-parse --short :/side1) &&
UNRELATED=$(git rev-parse --short :/unrelated) &&
MERGE=$(git rev-parse --short rebase_i_merge) &&
ONTO=$(git rev-parse --short main) &&
test_when_finished "git rebase --abort" &&
FAKE_LINES="1 2 3 5 6 7 8 9 10 11 4" &&
export FAKE_LINES &&
test_must_fail git rebase -ir main &&
cat >expect <<EOF &&
interactive rebase in progress; onto $ONTO
Last commands done (8 commands done):
pick $PICK side1
merge -C $MERGE rebase-i-merge-side # Merge branch '\''rebase_i_merge_side'\'' into rebase_i_merge
(see more in file .git/rebase-merge/done)
Next command to do (1 remaining command):
pick $UNRELATED unrelated
(use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch '\''rebase_i_merge'\'' on '\''$ONTO'\''.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)

Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: main.txt

no changes added to commit (use "git add" and/or "git commit -a")
EOF
git status --untracked-files=no >actual &&
test_cmp expect actual
'


test_expect_success 'status when rebasing -i in edit mode' '
git reset --hard main &&

View File

@ -1342,9 +1342,11 @@ static int split_commit_in_progress(struct wt_status *s)

/*
* Turn
* "pick d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some message"
* "pick d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some message" and
* "merge -C d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some-branch"
* into
* "pick d6a2f03 some message"
* "pick d6a2f03 some message" and
* "merge -C d6a2f03 some-branch"
*
* The function assumes that the line does not contain useless spaces
* before or after the command.
@ -1360,20 +1362,31 @@ static void abbrev_oid_in_line(struct strbuf *line)
starts_with(line->buf, "l "))
return;

split = strbuf_split_max(line, ' ', 3);
split = strbuf_split_max(line, ' ', 4);
if (split[0] && split[1]) {
struct object_id oid;
struct strbuf *hash;

if ((!strcmp(split[0]->buf, "merge ") ||
!strcmp(split[0]->buf, "m " ) ||
!strcmp(split[0]->buf, "fixup ") ||
!strcmp(split[0]->buf, "f " )) &&
(!strcmp(split[1]->buf, "-C ") ||
!strcmp(split[1]->buf, "-c "))) {
hash = split[2];
} else {
hash = split[1];
}
/*
* strbuf_split_max left a space. Trim it and re-add
* it after abbreviation.
*/
strbuf_trim(split[1]);
if (!repo_get_oid(the_repository, split[1]->buf, &oid)) {
strbuf_reset(split[1]);
strbuf_add_unique_abbrev(split[1], &oid,
strbuf_trim(hash);
if (!repo_get_oid(the_repository, hash->buf, &oid)) {
strbuf_reset(hash);
strbuf_add_unique_abbrev(hash, &oid,
DEFAULT_ABBREV);
strbuf_addch(split[1], ' ');
strbuf_addch(hash, ' ');
strbuf_reset(line);
for (i = 0; split[i]; i++)
strbuf_addbuf(line, split[i]);
@ -1731,6 +1744,7 @@ int wt_status_check_rebase(const struct worktree *wt,
struct wt_status_state *state)
{
struct stat st;
struct string_list have_done = STRING_LIST_INIT_DUP;

if (!stat(worktree_git_path(the_repository, wt, "rebase-apply"), &st)) {
if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/applying"), &st)) {
@ -1747,8 +1761,12 @@ int wt_status_check_rebase(const struct worktree *wt,
state->rebase_interactive_in_progress = 1;
else
state->rebase_in_progress = 1;
read_rebase_todolist("rebase-merge/done", &have_done);
if (have_done.nr > 0 && starts_with(have_done.items[have_done.nr - 1].string, "merge"))
state->merge_during_rebase_in_progress = 1;
state->branch = get_branch(wt, "rebase-merge/head-name");
state->onto = get_branch(wt, "rebase-merge/onto");
string_list_clear(&have_done, 0);
} else
return 0;
return 1;
@ -1842,10 +1860,15 @@ static void wt_longstatus_print_state(struct wt_status *s)

if (state->merge_in_progress) {
if (state->rebase_interactive_in_progress) {
show_rebase_information(s, state_color);
fputs("\n", s->fp);
}
show_merge_in_progress(s, state_color);
if (state->merge_during_rebase_in_progress)
show_rebase_in_progress(s, state_color);
else {
show_rebase_information(s, state_color);
fputs("\n", s->fp);
show_merge_in_progress(s, state_color);
}
} else
show_merge_in_progress(s, state_color);
} else if (state->am_in_progress)
show_am_in_progress(s, state_color);
else if (state->rebase_in_progress || state->rebase_interactive_in_progress)

View File

@ -87,6 +87,7 @@ struct wt_status_state {
int am_empty_patch;
int rebase_in_progress;
int rebase_interactive_in_progress;
int merge_during_rebase_in_progress;
int cherry_pick_in_progress;
int bisect_in_progress;
int revert_in_progress;