From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v5 0/4] checkout: 'autostash' for branch switching
Date: Sun, 15 Mar 2026 11:19:09 +0000 [thread overview]
Message-ID: <pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2234.v4.git.git.1773482375668.gitgitgadget@gmail.com>
cc: Phillip Wood phillip.wood123@gmail.com
Harald Nordgren (4):
stash: add --ours-label, --theirs-label, --base-label for apply
sequencer: allow create_autostash to run silently
sequencer: teach autostash apply to take optional conflict marker
labels
checkout: -m (--merge) uses autostash when switching branches
Documentation/git-checkout.adoc | 58 +++++-----
Documentation/git-stash.adoc | 11 +-
Documentation/git-switch.adoc | 27 ++---
builtin/checkout.c | 180 ++++++++++++++++++--------------
builtin/stash.c | 32 ++++--
sequencer.c | 67 +++++++++---
sequencer.h | 4 +
t/t3420-rebase-autostash.sh | 24 +++--
t/t3903-stash.sh | 18 ++++
t/t7201-co.sh | 160 ++++++++++++++++++++++++++++
t/t7600-merge.sh | 2 +-
xdiff-interface.c | 12 +++
xdiff-interface.h | 1 +
13 files changed, 447 insertions(+), 149 deletions(-)
base-commit: dc6ecd5354dca88d51b6d6562777fc8fc10d77e1
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v5
Pull-Request: https://github.com/git/git/pull/2234
Range-diff vs v4:
-: ---------- > 1: 00d8920498 stash: add --ours-label, --theirs-label, --base-label for apply
-: ---------- > 2: 5d176f1700 sequencer: allow create_autostash to run silently
-: ---------- > 3: 3d6829438a sequencer: teach autostash apply to take optional conflict marker labels
1: 5d49c0031a ! 4: 7f3735d40e checkout: -m (--merge) uses autostash when switching branches
@@ Documentation/git-checkout.adoc: working tree, by copying them from elsewhere, e
+ afterwards. If the local changes do not overlap with the
+ differences between branches, the switch proceeds without
+ stashing. If reapplying the stash results in conflicts, the
-+ entry is saved to the stash list so you can use `git stash
-+ pop` to recover and `git stash drop` when done.
++ entry is saved to the stash list. Resolve the conflicts
++ and run `git stash drop` when done, or clear the working
++ tree (e.g. with `git reset --hard`) before running `git stash
++ pop` later to re-apply your changes.
+
When checking out paths from the index, this option lets you recreate
the conflicted merge in the specified paths. This option cannot be
@@ 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 save the local
-+changes in a stash entry and reset the working tree to allow switching:
++You can give the `-m` flag to the command, which would carry your local
++changes to the new branch:
------------
$ git checkout -m mytopic
-Auto-merging frotz
-+Applied autostash.
++Switched to branch 'mytopic'
------------
-After this three-way merge, the local modifications are _not_
@@ Documentation/git-switch.adoc: variable.
+ local changes do not overlap with the differences between
+ branches, the switch proceeds without stashing. If
+ reapplying the stash results in conflicts, the entry is
-+ saved to the stash list so you can use `git stash pop` to
-+ recover and `git stash drop` when done.
++ saved to the stash list. Resolve the conflicts and run
++ `git stash drop` when done, or clear the working tree
++ (e.g. with `git reset --hard`) before running `git stash pop`
++ later to re-apply your changes.
`--conflict=<style>`::
The same as `--merge` option above, but changes the way the
@@ 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 save the local
-+changes in a stash entry and reset the working tree to allow switching:
++You can give the `-m` flag to the command, which would carry your local
++changes to the new branch:
------------
$ git switch -m mytopic
-Auto-merging frotz
-+Created autostash: 7a9afa3
-+Applied autostash.
++Switched to branch 'mytopic'
------------
-After this three-way merge, the local modifications are _not_
@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op
}
if (!cache_tree_fully_valid(the_repository->index->cache_tree))
-@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,
- if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))
- die(_("unable to write new index file"));
-
-- if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
-- show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
--
- return 0;
- }
-
@@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
release_revisions(&revs);
}
@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
struct object_id rev;
int flag, writeout_error = 0;
int do_merge = 1;
++ int created_autostash = 0;
+ struct strbuf old_commit_shortname = STRBUF_INIT;
+ const char *stash_label_ancestor = NULL;
@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
+ if (repo_read_index(the_repository) < 0)
+ die(_("index file corrupt"));
+ if (checkout_would_clobber_changes(&old_branch_info,
-+ new_branch_info))
++ new_branch_info)) {
+ create_autostash_ref_silent(the_repository,
+ "CHECKOUT_AUTOSTASH");
++ created_autostash = 1;
++ }
+ }
+
if (do_merge) {
@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
+ if (repo_read_index(the_repository) < 0)
+ die(_("index file corrupt"));
+
-+ if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
++ if (created_autostash && !opts->discard_changes && !opts->quiet &&
++ new_branch_info->commit)
+ show_local_changes(&new_branch_info->commit->object,
+ &opts->diff_options);
+
@@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char
return ret;
## sequencer.c ##
-@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)
-
- static void create_autostash_internal(struct repository *r,
- const char *path,
-- const char *refname)
-+ const char *refname,
-+ int silent)
- {
- struct strbuf buf = STRBUF_INIT;
- struct lock_file lock_file = LOCK_INIT;
-@@ sequencer.c: static void create_autostash_internal(struct repository *r,
- &oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);
- }
-
-- printf(_("Created autostash: %s\n"), buf.buf);
-+ if (!silent)
-+ fprintf(stderr, _("Created autostash: %s\n"), buf.buf);
- if (reset_head(r, &ropts) < 0)
- die(_("could not reset --hard"));
- discard_index(r->index);
-@@ sequencer.c: static void create_autostash_internal(struct repository *r,
-
- void create_autostash(struct repository *r, const char *path)
- {
-- create_autostash_internal(r, path, NULL);
-+ create_autostash_internal(r, path, NULL, 0);
- }
-
- void create_autostash_ref(struct repository *r, const char *refname)
- {
-- create_autostash_internal(r, NULL, refname);
-+ create_autostash_internal(r, NULL, refname, 0);
- }
-
--static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
-+void create_autostash_ref_silent(struct repository *r, const char *refname)
-+{
-+ create_autostash_internal(r, NULL, refname, 1);
-+}
-+
-+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
-+ const char *label1, const char *label2,
-+ const char *label_ancestor)
- {
- struct child_process child = CHILD_PROCESS_INIT;
- int ret = 0;
-@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
- child.no_stderr = 1;
- strvec_push(&child.args, "stash");
- strvec_push(&child.args, "apply");
-+ if (label1)
-+ strvec_pushf(&child.args, "--ours-label=%s", label1);
-+ if (label2)
-+ strvec_pushf(&child.args, "--theirs-label=%s", label2);
-+ if (label_ancestor)
-+ strvec_pushf(&child.args, "--base-label=%s", label_ancestor);
- strvec_push(&child.args, stash_oid);
- ret = run_command(&child);
- }
-@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
+@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
strvec_push(&store.args, stash_oid);
if (run_command(&store))
ret = error(_("cannot store %s"), stash_oid);
@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte
}
return ret;
-@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)
- }
- strbuf_trim(&stash_oid);
-
-- ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);
-+ ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,
-+ NULL, NULL, NULL);
-
- unlink(path);
- strbuf_release(&stash_oid);
-@@ sequencer.c: int apply_autostash(const char *path)
-
- int apply_autostash_oid(const char *stash_oid)
- {
-- return apply_save_autostash_oid(stash_oid, 1);
-+ return apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);
- }
-
- static int apply_save_autostash_ref(struct repository *r, const char *refname,
-- int attempt_apply)
-+ int attempt_apply,
-+ const char *label1, const char *label2,
-+ const char *label_ancestor)
- {
- struct object_id stash_oid;
- char stash_oid_hex[GIT_MAX_HEXSZ + 1];
-@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,
- return error(_("autostash reference is a symref"));
-
- oid_to_hex_r(stash_oid_hex, &stash_oid);
-- ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);
-+ ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,
-+ label1, label2, label_ancestor);
-
- refs_delete_ref(get_main_ref_store(r), "", refname,
- &stash_oid, REF_NO_DEREF);
-@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,
-
- int save_autostash_ref(struct repository *r, const char *refname)
- {
-- return apply_save_autostash_ref(r, refname, 0);
-+ return apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);
- }
-
- int apply_autostash_ref(struct repository *r, const char *refname)
- {
-- return apply_save_autostash_ref(r, refname, 1);
-+ return apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);
-+}
-+
-+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,
-+ const char *label1, const char *label2,
-+ const char *label_ancestor)
-+{
-+ return apply_save_autostash_ref(r, refname, 1,
-+ label1, label2, label_ancestor);
- }
-
- static int checkout_onto(struct repository *r, struct replay_opts *opts,
-
- ## sequencer.h ##
-@@ sequencer.h: void commit_post_rewrite(struct repository *r,
-
- void create_autostash(struct repository *r, const char *path);
- void create_autostash_ref(struct repository *r, const char *refname);
-+void create_autostash_ref_silent(struct repository *r, const char *refname);
- int save_autostash(const char *path);
- int save_autostash_ref(struct repository *r, const char *refname);
- int apply_autostash(const char *path);
- int apply_autostash_oid(const char *stash_oid);
- int apply_autostash_ref(struct repository *r, const char *refname);
-+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,
-+ const char *label1, const char *label2,
-+ const char *label_ancestor);
-
- #define SUMMARY_INITIAL_COMMIT (1 << 0)
- #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
## t/t3420-rebase-autostash.sh ##
@@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {
@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
git reset --hard &&
## t/t7600-merge.sh ##
-@@ t/t7600-merge.sh: test_expect_success 'merge --squash --autostash conflict does not attempt to app
- >unrelated &&
- git add unrelated &&
- test_must_fail git merge --squash c7 --autostash >out 2>err &&
-- ! grep "Applying autostash resulted in conflicts." err &&
-+ ! grep "resulted in conflicts" err &&
- grep "When finished, apply stashed changes with \`git stash pop\`" out
- '
-
@@ t/t7600-merge.sh: test_expect_success 'merge with conflicted --autostash changes' '
git diff >expect &&
test_when_finished "test_might_fail git stash drop" &&
git merge --autostash c3 2>err &&
- test_grep "Applying autostash resulted in conflicts." err &&
-+ test_grep "resulted in conflicts" err &&
++ test_grep "your local changes resulted in conflicts" err &&
git show HEAD:file >merge-result &&
test_cmp result.1-9 merge-result &&
git stash show -p >actual &&
--
gitgitgadget
next prev parent reply other threads:[~2026-03-15 11:19 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-12 13:26 [PATCH] checkout: add --autostash option for branch switching Harald Nordgren via GitGitGadget
2026-03-12 14:40 ` Junio C Hamano
2026-03-12 19:33 ` [PATCH v31 0/2] status: add status.compareBranches config for multiple branch comparisons Harald Nordgren
2026-03-13 14:29 ` [PATCH] checkout: add --autostash option for branch switching Phillip Wood
2026-03-14 17:17 ` Junio C Hamano
2026-03-16 16:36 ` Phillip Wood
2026-03-16 20:04 ` Junio C Hamano
2026-03-17 9:47 ` Harald Nordgren
2026-03-19 8:25 ` Harald Nordgren
2026-03-19 16:48 ` Junio C Hamano
2026-03-12 19:33 ` [PATCH v2] " Harald Nordgren via GitGitGadget
2026-03-12 19:50 ` Junio C Hamano
2026-03-13 9:22 ` [PATCH] " Harald Nordgren
2026-03-13 9:23 ` [PATCH v3] " Harald Nordgren via GitGitGadget
2026-03-13 17:16 ` Junio C Hamano
2026-03-13 19:33 ` [PATCH] " Harald Nordgren
2026-03-13 20:30 ` Junio C Hamano
2026-03-14 9:59 ` [PATCH v4] checkout: -m (--merge) uses autostash when switching branches Harald Nordgren via GitGitGadget
2026-03-15 2:25 ` Junio C Hamano
2026-03-15 11:19 ` Harald Nordgren via GitGitGadget [this message]
2026-03-15 11:19 ` [PATCH v5 1/4] stash: add --ours-label, --theirs-label, --base-label for apply Harald Nordgren via GitGitGadget
2026-03-15 11:19 ` [PATCH v5 2/4] sequencer: allow create_autostash to run silently Harald Nordgren via GitGitGadget
2026-03-15 11:19 ` [PATCH v5 3/4] sequencer: teach autostash apply to take optional conflict marker labels Harald Nordgren via GitGitGadget
2026-03-15 11:19 ` [PATCH v5 4/4] checkout: -m (--merge) uses autostash when switching branches Harald Nordgren via GitGitGadget
2026-03-17 9:35 ` [PATCH v6 0/4] checkout: 'autostash' for branch switching Harald Nordgren via GitGitGadget
2026-03-17 9:35 ` [PATCH v6 1/4] stash: add --ours-label, --theirs-label, --base-label for apply Harald Nordgren via GitGitGadget
2026-03-17 9:35 ` [PATCH v6 2/4] sequencer: allow create_autostash to run silently Harald Nordgren via GitGitGadget
2026-03-17 9:35 ` [PATCH v6 3/4] sequencer: teach autostash apply to take optional conflict marker labels Harald Nordgren via GitGitGadget
2026-03-17 9:35 ` [PATCH v6 4/4] checkout: -m (--merge) uses autostash when switching branches Harald Nordgren via GitGitGadget
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=pull.2234.v5.git.git.1773573553.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