From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>,
Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH 2/2] checkout -m: refine autostash fallback
Date: Sat, 25 Jul 2026 15:34:29 +0000 [thread overview]
Message-ID: <37becf38c2ef175a3dadcf750e2cca836942d83e.1784993669.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2364.git.git.1784993669.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
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>
---
builtin/checkout.c | 17 +++++++++++++----
t/t7201-co.sh | 17 ++++++++++++++++-
2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 72aafa4049..2e8b2a2348 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;
}
}
@@ -1166,6 +1172,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;
@@ -1243,7 +1250,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);
@@ -1256,6 +1263,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) {
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 0ddd1ad7aa..f9696dab36 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 &&
--
gitgitgadget
prev parent reply other threads:[~2026-07-25 15:34 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-25 15:34 [PATCH 0/2] checkout -m: refine autostash fallback Harald Nordgren via GitGitGadget
2026-07-25 15:34 ` [PATCH 1/2] sequencer: teach autostash apply to report conflicts Harald Nordgren via GitGitGadget
2026-07-25 15:34 ` Harald Nordgren via GitGitGadget [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=37becf38c2ef175a3dadcf750e2cca836942d83e.1784993669.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=haraldnordgren@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox