Re: [PATCH v16 0/5] checkout: 'autostash' for branch switching
- From
- Phillip Wood <phillip.wood123@gmail.com>
- Date
- Apr 29, 2026, 10:02 UTC
- Message-ID
- <3170928a-e05e-42cc-b15e-56e0fa479066@gmail.com>
- In-Reply-To
- <pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com>
Hi Harald
On 28/04/2026 19:39, Harald Nordgren via GitGitGadget wrote:
Show 16 quoted lines
> * Updated the git checkout and git switch docs to show the actual output > when using -m to carry local changes across a branch switch, and changed > "would carry" to "will carry". > * Rewrote the merge-conflict example in both docs to match the new, more > concise message printed on autostash conflicts. > * Replaced the show_unpack_errors flag and old_commit parameter in > merge_working_tree()/init_topts() with a plain quiet boolean, so the > caller decides directly whether to suppress unpack errors. > * Tightened the 'checkout -m with dirty tree' test by replacing a printf > with a heredoc. > * Made the 'checkout -m creates a recoverable stash on conflict' test also > assert the reflog subject of the new stash entry. > * Replaced two test_grep calls in the 'checkout -m which would overwrite > untracked file' test with a single test_cmp, which also catches a > regression where the "would be overwritten" message could end up printed > twice.
That all sounds good and the range-diff below looks as I would expect it to. I've left some suggestions for possible future work on patch 5 but I think this is ready to be merged as-is.
Thanks for working on it
Phillip
Show 246 quoted lines
> Harald Nordgren (5):
> stash: add --label-ours, --label-theirs, --label-base for apply
> sequencer: allow create_autostash to run silently
> sequencer: teach autostash apply to take optional conflict marker
> labels
> checkout: rollback lock on early returns in merge_working_tree
> checkout -m: autostash when switching branches
>
> Documentation/git-checkout.adoc | 55 +++++------
> Documentation/git-stash.adoc | 11 ++-
> Documentation/git-switch.adoc | 36 ++++---
> builtin/checkout.c | 166 +++++++++++++++-----------------
> builtin/commit.c | 3 +-
> builtin/merge.c | 15 ++-
> builtin/stash.c | 28 ++++--
> sequencer.c | 69 +++++++++----
> sequencer.h | 7 +-
> t/t3420-rebase-autostash.sh | 16 +--
> t/t3903-stash.sh | 24 +++++
> t/t7201-co.sh | 71 +++++++++++++-
> t/t7600-merge.sh | 3 +-
> xdiff-interface.c | 12 +++
> xdiff-interface.h | 1 +
> xdiff/xmerge.c | 6 +-
> 16 files changed, 343 insertions(+), 180 deletions(-)
>
>
> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v16
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v16
> Pull-Request: https://github.com/git/git/pull/2234
>
> Range-diff vs v15:
>
> 1: aba8e6a9dc = 1: aba8e6a9dc stash: add --label-ours, --label-theirs, --label-base for apply
> 2: 89e0bfa803 = 2: 89e0bfa803 sequencer: allow create_autostash to run silently
> 3: a428ce7328 = 3: a428ce7328 sequencer: teach autostash apply to take optional conflict marker labels
> 4: f358424085 = 4: f358424085 checkout: rollback lock on early returns in merge_working_tree
> 5: 96b14db827 ! 5: 07d25fda91 checkout -m: autostash when switching branches
> @@ Documentation/git-checkout.adoc: $ git checkout mytopic
>
> -You can give the `-m` flag to the command, which would try a
> -three-way merge:
> -+You can give the `-m` flag to the command, which would carry your local
> ++You can give the `-m` flag to the command, which will carry your local
> +changes to the new branch:
>
> ------------
> $ git checkout -m mytopic
> -Auto-merging frotz
> ++Applied autostash.
> +Switched to branch 'mytopic'
> ++The following paths have local changes:
> ++M frotz
> ------------
>
> -After this three-way merge, the local modifications are _not_
> @@ Documentation/git-checkout.adoc: $ git checkout mytopic
>
> -When a merge conflict happens during switching branches with
> -the `-m` option, you would see something like this:
> -+When the `--merge` (`-m`) option is in effect and the locally
> -+modified files overlap with files that need to be updated by the
> -+branch switch, the changes are stashed and reapplied after the
> -+switch. If this process results in conflicts, a stash entry is saved
> -+and made available in `git stash list`:
> ++When the `--merge` (`-m`) option is given and the local changes
> ++overlap with the changes in the branch we're switching to, the
> ++changes are stashed and reapplied after the switch. If this
> ++process results in conflicts, the stash entry is saved and a
> ++message is printed:
>
> ------------
> $ git checkout -m mytopic
> @@ Documentation/git-checkout.adoc: $ git checkout mytopic
> -ERROR: Merge conflict in frotz
> -fatal: merge program failed
> -------------
> -+Your local changes are stashed, however, applying it to carry
> -+forward your local changes resulted in conflicts:
> -
> +-
> -At this point, `git diff` shows the changes cleanly merged as in
> -the previous example, as well as the changes in the conflicted
> -files. Edit and resolve the conflict and mark it resolved with
> -`git add` as usual:
> -+ - You can try resolving them now. If you resolved them
> -+ successfully, discard the stash entry with "git stash drop".
> -
> -+ - Alternatively you can "git reset --hard" if you do not want
> -+ to deal with them right now, and later "git stash pop" to
> -+ recover your local changes.
> - ------------
> +-
> +-------------
> -$ edit frotz
> -$ git add frotz
> --------------
> -+
> -+You can try resolving the conflicts now. Edit the conflicting files
> -+and mark them resolved with `git add` as usual, then run `git stash
> -+drop` to discard the stash entry. Alternatively, you can clear the
> -+working tree with `git reset --hard` and recover your local changes
> -+later with `git stash pop`.
> ++Your local changes are stashed, however applying them
> ++resulted in conflicts. You can either resolve the conflicts
> ++and then discard the stash with "git stash drop", or, if you
> ++do not want to resolve them now, run "git reset --hard" and
> ++apply the local changes later by running "git stash pop".
> + ------------
>
> CONFIGURATION
> - -------------
>
> ## Documentation/git-switch.adoc ##
> @@ Documentation/git-switch.adoc: variable.
> @@ Documentation/git-switch.adoc: $ git switch mytopic
>
> -You can give the `-m` flag to the command, which would try a three-way
> -merge:
> -+You can give the `-m` flag to the command, which would carry your local
> ++You can give the `-m` flag to the command, which will carry your local
> +changes to the new branch:
>
> ------------
> $ git switch -m mytopic
> -Auto-merging frotz
> ++Applied autostash.
> +Switched to branch 'mytopic'
> ++The following paths have local changes:
> ++M frotz
> ------------
>
> -After this three-way merge, the local modifications are _not_
> @@ builtin/checkout.c: struct checkout_opts {
> char *name; /* The short name used */
> char *path; /* The full name of a real branch */
> @@ builtin/checkout.c: static void setup_branch_path(struct branch_info *branch)
> + branch->path = strbuf_detach(&buf, NULL);
> + }
>
> - static void init_topts(struct unpack_trees_options *topts, int merge,
> +-static void init_topts(struct unpack_trees_options *topts, int merge,
> ++static void init_topts(struct unpack_trees_options *topts,
> int show_progress, int overwrite_ignore,
> - struct commit *old_commit)
> -+ struct commit *old_commit, bool show_unpack_errors)
> ++ bool quiet)
> {
> memset(topts, 0, sizeof(*topts));
> topts->head_idx = -1;
> @@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i
> topts->update = 1;
> topts->merge = 1;
> - topts->quiet = merge && old_commit;
> -+ topts->quiet = merge && old_commit && !show_unpack_errors;
> ++ topts->quiet = quiet;
> topts->verbose_update = show_progress;
> topts->fn = twoway_merge;
> topts->preserve_ignored = !overwrite_ignore;
> @@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i
> static int merge_working_tree(const struct checkout_opts *opts,
> struct branch_info *old_branch_info,
> struct branch_info *new_branch_info,
> -- int *writeout_error)
> -+ int *writeout_error,
> -+ bool show_unpack_errors)
> ++ bool quiet,
> + int *writeout_error)
> {
> int ret;
> - struct lock_file lock_file = LOCK_INIT;
> @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,
> + }
>
> /* 2-way merge to the new branch */
> - init_topts(&topts, opts->merge, opts->show_progress,
> +- init_topts(&topts, opts->merge, opts->show_progress,
> - opts->overwrite_ignore, old_branch_info->commit);
> -+ opts->overwrite_ignore, old_branch_info->commit,
> -+ show_unpack_errors);
> ++ init_topts(&topts, opts->show_progress,
> ++ opts->overwrite_ignore, quiet);
> init_checkout_metadata(&topts.meta, new_branch_info->refname,
> new_branch_info->commit ?
> &new_branch_info->commit->object.oid :
> @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
> if (do_merge) {
> - ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
> + ret = merge_working_tree(opts, &old_branch_info, new_branch_info,
> -+ &writeout_error, false);
> ++ opts->merge, &writeout_error);
> + if (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {
> + strbuf_addf(&autostash_msg,
> + "autostash while switching to '%s'",
> @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
> + autostash_msg.buf, true);
> + created_autostash = 1;
> + ret = merge_working_tree(opts, &old_branch_info, new_branch_info,
> -+ &writeout_error, true);
> ++ false, &writeout_error);
> + }
> + if (created_autostash) {
> + if (opts->conflict_style >= 0) {
> @@ t/t7201-co.sh: test_expect_success 'checkout -m with dirty tree' '
> test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
>
> - printf "M\t%s\n" one >expect.messages &&
> -+ printf "The following paths have local changes:\nM\t%s\n" one >expect.messages &&
> ++ cat >expect.messages <<-\EOF &&
> ++ The following paths have local changes:
> ++ M one
> ++ EOF
> test_cmp expect.messages messages &&
>
> fill "M one" "A three" "D two" >expect.main &&
> @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
> + test_grep "git stash drop" actual &&
> + test_grep "git stash pop" actual &&
> + test_grep "The following paths have local changes" actual &&
> -+ git show --format=%B --diff-merges=1 refs/stash >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 &&
> ++ autostash while switching to ${SQ}side${SQ}
> + On main: autostash while switching to ${SQ}side${SQ}
> +
> + diff --git a/one b/one
> @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
> + >another-file.t &&
> + fill 1 2 3 4 5 >one &&
> + test_must_fail git checkout -m @{-1} 2>err &&
> -+ test_grep "would be overwritten by checkout" err &&
> -+ test_grep "another-file.t" err
> ++ q_to_tab >expect <<-\EOF &&
> ++ error: The following untracked working tree files would be overwritten by checkout:
> ++ Qanother-file.t
> ++ Please move or remove them before you switch branches.
> ++ Aborting
> ++ Applied autostash.
> ++ EOF
> ++ test_cmp expect err
> +'
> +
> test_expect_success 'switch to another branch while carrying a deletion' '
>