Subject: [PATCH v11 0/4] checkout: 'autostash' for branch switching

From: "Harald Nordgren via GitGitGadget" <[email-masked]>
Date: Tue, 14 Apr 2026 10:50:15 +0000

Harald Nordgren (4):
  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: -m (--merge) uses autostash when switching branches

 Documentation/git-checkout.adoc |  58 ++++-----
 Documentation/git-stash.adoc    |  11 +-
 Documentation/git-switch.adoc   |  33 ++---
 builtin/checkout.c              | 147 ++++++++++------------
 builtin/stash.c                 |  32 +++--
 sequencer.c                     |  87 ++++++++++---
 sequencer.h                     |   8 ++
 t/t3420-rebase-autostash.sh     |  24 +++-
 t/t3903-stash.sh                |  29 +++++
 t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++
 t/t7600-merge.sh                |   2 +-
 xdiff-interface.c               |  12 ++
 xdiff-interface.h               |   1 +
 xdiff/xmerge.c                  |   6 +-
 14 files changed, 499 insertions(+), 159 deletions(-)

base-commit: 9e8f4e9c04e3efa494e78b710e0c5f6cc77a0a5e
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v11
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v11
Pull-Request: https://github.com/git/git/pull/2234

Range-diff vs v10:

 1:  aa519c50be = 1:  9ab5431b47 stash: add --label-ours, --label-theirs, --label-base for apply
 2:  8173a4eb72 = 2:  e7f8328e3c sequencer: allow create_autostash to run silently
 3:  f36fedd8c2 = 3:  3242fd3261 sequencer: teach autostash apply to take optional conflict marker labels
 4:  b6e5546d74 ! 4:  97a5d87c81 checkout: -m (--merge) uses autostash when switching branches
     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
       	int do_merge = 1;
      +	int created_autostash = 0;
      +	struct strbuf old_commit_shortname = STRBUF_INIT;
     ++	struct strbuf autostash_msg = STRBUF_INIT;
      +	const char *stash_label_base = NULL;
       
       	trace2_cmd_mode("branch");
     @@ 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);
      +		if (ret && opts->merge) {
     -+			create_autostash_ref_silent(the_repository,
     -+						   "CHECKOUT_AUTOSTASH_HEAD");
     ++			strbuf_addf(&autostash_msg,
     ++				    "autostash while switching to '%s'",
     ++				    new_branch_info->name);
     ++			create_autostash_ref_silent_with_msg(the_repository,
     ++							    "CHECKOUT_AUTOSTASH_HEAD",
     ++							    autostash_msg.buf);
      +			created_autostash = 1;
      +			ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
      +		}
     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
      +						       "CHECKOUT_AUTOSTASH_HEAD",
      +						       new_branch_info->name,
      +						       "local",
     -+						       stash_label_base);
     ++						       stash_label_base,
     ++						       autostash_msg.len ? autostash_msg.buf : NULL);
       			branch_info_release(&old_branch_info);
      +			strbuf_release(&old_commit_shortname);
     ++			strbuf_release(&autostash_msg);
       			return ret;
       		}
       	}
     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
      +	}
      +	apply_autostash_ref_with_labels(the_repository, "CHECKOUT_AUTOSTASH_HEAD",
      +				       new_branch_info->name, "local",
     -+				       stash_label_base);
     ++				       stash_label_base,
     ++				       autostash_msg.len ? autostash_msg.buf : NULL);
      +
      +	discard_index(the_repository->index);
      +	if (repo_read_index(the_repository) < 0)
     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
       	ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
       	branch_info_release(&old_branch_info);
      +	strbuf_release(&old_commit_shortname);
     ++	strbuf_release(&autostash_msg);
       
       	return ret || writeout_error;
       }
      
       ## 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 *message,
     + 				      bool silent)
     + {
     + 	struct strbuf buf = STRBUF_INIT;
     +@@ sequencer.c: static void create_autostash_internal(struct repository *r,
     + 		struct object_id oid;
     + 
     + 		strvec_pushl(&stash.args,
     +-			     "stash", "create", "autostash", NULL);
     ++			     "stash", "create",
     ++			     message ? message : "autostash", NULL);
     + 		stash.git_cmd = 1;
     + 		stash.no_stdin = 1;
     + 		strbuf_reset(&buf);
     +@@ 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, false);
     ++	create_autostash_internal(r, path, NULL, NULL, false);
     + }
     + 
     + void create_autostash_ref(struct repository *r, const char *refname)
     + {
     +-	create_autostash_internal(r, NULL, refname, false);
     ++	create_autostash_internal(r, NULL, refname, NULL, false);
     + }
     + 
     + void create_autostash_ref_silent(struct repository *r, const char *refname)
     + {
     +-	create_autostash_internal(r, NULL, refname, true);
     ++	create_autostash_internal(r, NULL, refname, NULL, true);
     ++}
     ++
     ++void create_autostash_ref_silent_with_msg(struct repository *r,
     ++					  const char *refname,
     ++					  const char *message)
     ++{
     ++	create_autostash_internal(r, NULL, refname, message, true);
     + }
     + 
     + static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
     + 				    const char *label_ours, const char *label_theirs,
     +-				    const char *label_base)
     ++				    const char *label_base,
     ++				    const char *stash_msg)
     + {
     + 	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,
     + 		strvec_push(&store.args, "stash");
     + 		strvec_push(&store.args, "store");
     + 		strvec_push(&store.args, "-m");
     +-		strvec_push(&store.args, "autostash");
     ++		strvec_push(&store.args, stash_msg ? stash_msg : "autostash");
     + 		strvec_push(&store.args, "-q");
       		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,
     +-				      NULL, NULL, NULL);
     ++				      NULL, 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, NULL, NULL, NULL);
     ++	return apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);
     + }
     + 
     + static int apply_save_autostash_ref(struct repository *r, const char *refname,
     + 				    int attempt_apply,
     + 				    const char *label_ours, const char *label_theirs,
     +-				    const char *label_base)
     ++				    const char *label_base,
     ++				    const char *stash_msg)
     + {
     + 	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,
     + 
     + 	oid_to_hex_r(stash_oid_hex, &stash_oid);
     + 	ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,
     +-				       label_ours, label_theirs, label_base);
     ++				       label_ours, label_theirs, label_base,
     ++				       stash_msg);
     + 
     + 	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, NULL, NULL, NULL);
     ++	return apply_save_autostash_ref(r, refname, 0,
     ++					NULL, NULL, NULL, NULL);
     + }
     + 
     + int apply_autostash_ref(struct repository *r, const char *refname)
     + {
     +-	return apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);
     ++	return apply_save_autostash_ref(r, refname, 1,
     ++					NULL, NULL, NULL, NULL);
     + }
     + 
     + int apply_autostash_ref_with_labels(struct repository *r, const char *refname,
     + 				    const char *label_ours, const char *label_theirs,
     +-				    const char *label_base)
     ++				    const char *label_base,
     ++				    const char *stash_msg)
     + {
     + 	return apply_save_autostash_ref(r, refname, 1,
     +-					label_ours, label_theirs, label_base);
     ++					label_ours, label_theirs, label_base,
     ++					stash_msg);
     + }
     + 
     + 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);
     ++void create_autostash_ref_silent_with_msg(struct repository *r,
     ++					  const char *refname,
     ++					  const char *message);
     + int save_autostash(const char *path);
     + int save_autostash_ref(struct repository *r, const char *refname);
     + int apply_autostash(const char *path);
     +@@ sequencer.h: 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 *label_ours, const char *label_theirs,
     +-				    const char *label_base);
     ++				    const char *label_base,
     ++				    const char *stash_msg);
     + 
     + #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>'
      +	test_cmp expect one
      +'
      +
     ++test_expect_success 'checkout -m autostash message includes target branch' '
     ++	git checkout -f main &&
     ++	git clean -f &&
     ++
     ++	fill 1 2 3 4 5 >one &&
     ++	git checkout -m side >actual 2>&1 &&
     ++	git stash list >stash-list &&
     ++	test_grep "autostash while switching to .side." stash-list &&
     ++	git stash drop &&
     ++	git checkout -f main &&
     ++	git reset --hard
     ++'
     ++
      +test_expect_success 'checkout -m stashes on staged conflicting changes' '
      +	git checkout -f main &&
      +	git clean -f &&

-- 
gitgitgadget