@leroysheep / MovieWebApp / commits / f4996ed461

checkout -m: refine autostash fallback

When unpack_trees() fails under "git checkout -m", only create an autostash and retry if there are tracked local changes. Without such changes, the fallback cannot help and merely repeats the same failure. Use the conflict result from apply_autostash_ref() to print a blank line before the branch-switch message, visually separating it from the conflict advice. Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Harald Nordgren committed Jul 25, 2026 at 15:34 UTC f4996ed4619836f4108520a4721a0a781298b644
2 files changed +29 -5
builtin/checkout.c
+13 -4
index 5858d1fc30..d2ca09b673 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -838,10 +838,11 @@ static void init_topts(struct unpack_trees_options *topts, static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, - bool quiet, + bool allow_autostash, int *writeout_error) { int ret; + bool can_autostash = false; struct lock_file lock_file = LOCK_INIT; struct tree *new_tree; @@ -888,9 +889,13 @@ static int merge_working_tree(const struct checkout_opts *opts, return 1; } + if (allow_autostash) + can_autostash = has_unstaged_changes(the_repository, 1) || + has_uncommitted_changes(the_repository, 1); + /* 2-way merge to the new branch */ init_topts(&topts, opts->show_progress, - opts->overwrite_ignore, quiet); + opts->overwrite_ignore, can_autostash); init_checkout_metadata(&topts.meta, new_branch_info->refname, new_branch_info->commit ? &new_branch_info->commit->object.oid : @@ -917,7 +922,8 @@ static int merge_working_tree(const struct checkout_opts *opts, clear_unpack_trees_porcelain(&topts); if (ret == -1) { rollback_lock_file(&lock_file); - return MERGE_WORKING_TREE_UNPACK_FAILED; + return can_autostash ? + MERGE_WORKING_TREE_UNPACK_FAILED : 1; } } @@ -1163,6 +1169,7 @@ static int switch_branches(const struct checkout_opts *opts, int flag, writeout_error = 0; int do_merge = 1; int created_autostash = 0; + bool autostash_conflicted = false; struct strbuf old_commit_shortname = STRBUF_INIT; struct strbuf autostash_msg = STRBUF_INIT; const char *stash_label_base = NULL; @@ -1240,7 +1247,7 @@ static int switch_branches(const struct checkout_opts *opts, "local", stash_label_base, autostash_msg.buf, - NULL); + &autostash_conflicted); } if (ret) { branch_info_release(&old_branch_info); @@ -1253,6 +1260,8 @@ static int switch_branches(const struct checkout_opts *opts, if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit) orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit); + if (autostash_conflicted && !opts->quiet) + fputc('\n', stderr); update_refs_for_switch(opts, &old_branch_info, new_branch_info); if (created_autostash) {
t/t7201-co.sh
+16 -1
index 7613b1d2a4..52b2010d73 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -240,6 +240,14 @@ test_expect_success 'checkout -m creates a recoverable stash on conflict' ' test_grep "git stash drop" actual && test_grep "git stash pop" actual && test_grep "The following paths have local changes" actual && + sed -n "/apply the local changes later/,/Switched to branch/p" \ + actual >separator.actual && + cat >separator.expect <<-EOF && + apply the local changes later by running "git stash pop". + + Switched to branch ${SQ}side${SQ} + EOF + test_cmp separator.expect separator.actual && git log -p -1 --format="%gs%n%B" -g --diff-merges=1 refs/stash >actual && sed /^index/d actual >actual.trimmed && cat >expect <<-EOF && @@ -262,11 +270,18 @@ test_expect_success 'checkout -m creates a recoverable stash on conflict' ' git reset --hard ' -test_expect_success 'checkout -m which would overwrite untracked file' ' +test_expect_success 'checkout -m only retries untracked-file failure with local changes' ' git checkout -f --detach main && test_commit another-file && git checkout HEAD^ && >another-file.t && + test_must_fail env GIT_TRACE2_EVENT="$(pwd)/trace" \ + git checkout -m @{-1} 2>err && + test_grep "untracked working tree files" err && + grep "\"region_enter\".*\"category\":\"index\",\"label\":\"refresh\"" \ + trace >refresh.events && + test_line_count = 1 refresh.events && + fill 1 2 3 4 5 >one && test_must_fail git checkout -m @{-1} 2>err && q_to_tab >expect <<-\EOF &&