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

From: "Harald Nordgren via GitGitGadget" <[email-masked]>
Date: Thu, 09 Apr 2026 19:17:11 +0000

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   |  33 +++---
 builtin/checkout.c              | 138 ++++++++++------------
 builtin/stash.c                 |  32 ++++--
 sequencer.c                     |  67 ++++++++---
 sequencer.h                     |   4 +
 t/t3420-rebase-autostash.sh     |  24 +++-
 t/t3903-stash.sh                |  37 ++++++
 t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++
 t/t7600-merge.sh                |   2 +-
 xdiff-interface.c               |  12 ++
 xdiff-interface.h               |   1 +
 xdiff/xmerge.c                  |   6 +-
 14 files changed, 463 insertions(+), 157 deletions(-)

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

Range-diff vs v7:

 1:  284075600a ! 1:  8fcf377820 stash: add --ours-label, --theirs-label, --base-label for apply
     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit
      +		echo upstream >file &&
      +		git add file &&
      +		git commit -m upstream &&
     -+		test_must_fail git stash apply --ours-label=UP --theirs-label=STASH &&
     -+		grep "^<<<<<<< UP" file &&
     -+		grep "^>>>>>>> STASH" file
     ++		test_must_fail git -c merge.conflictStyle=diff3 stash apply --ours-label=UP --theirs-label=STASH &&
     ++		test_grep "^<<<<<<< UP" file &&
     ++		test_grep "^||||||| Stash base" file &&
     ++		test_grep "^>>>>>>> STASH" file
     ++	)
     ++'
     ++
     ++test_expect_success 'apply with empty conflict labels' '
     ++	git init empty_labels &&
     ++	(
     ++		cd empty_labels &&
     ++		echo base >file &&
     ++		git add file &&
     ++		git commit -m base &&
     ++		echo stashed >file &&
     ++		git stash push -m "stashed" &&
     ++		echo upstream >file &&
     ++		git add file &&
     ++		git commit -m upstream &&
     ++		test_must_fail git stash apply --ours-label= --theirs-label= &&
     ++		test_grep "^<<<<<<<$" file &&
     ++		test_grep "^>>>>>>>$" file
      +	)
      +'
      +
       test_expect_success 'stash create reports a locked index' '
       	test_when_finished "rm -rf repo" &&
       	git init repo &&
     +
     + ## xdiff/xmerge.c ##
     +@@ xdiff/xmerge.c: static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
     + 			      int size, int i, int style,
     + 			      xdmerge_t *m, char *dest, int marker_size)
     + {
     +-	int marker1_size = (name1 ? strlen(name1) + 1 : 0);
     +-	int marker2_size = (name2 ? strlen(name2) + 1 : 0);
     +-	int marker3_size = (name3 ? strlen(name3) + 1 : 0);
     ++	int marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);
     ++	int marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);
     ++	int marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);
     + 	int needs_cr = is_cr_needed(xe1, xe2, m);
     + 
     + 	if (marker_size <= 0)
 2:  64261e3cb6 = 2:  86cf68d024 sequencer: allow create_autostash to run silently
 3:  c0d6b4b4c0 ! 3:  78300e0e9a sequencer: teach autostash apply to take optional conflict marker labels
     @@ sequencer.c: void create_autostash_ref_silent(struct repository *r, const char *
       
      -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
      +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,
     -+				    const char *label1, const char *label2,
     -+				    const char *label_ancestor)
     ++				    const char *label_ours, const char *label_theirs,
     ++				    const char *label_base)
       {
       	struct child_process child = CHILD_PROCESS_INIT;
       	int ret = 0;
     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte
       		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);
     ++		if (label_ours)
     ++			strvec_pushf(&child.args, "--ours-label=%s", label_ours);
     ++		if (label_theirs)
     ++			strvec_pushf(&child.args, "--theirs-label=%s", label_theirs);
     ++		if (label_base)
     ++			strvec_pushf(&child.args, "--base-label=%s", label_base);
       		strvec_push(&child.args, stash_oid);
       		ret = run_command(&child);
       	}
     @@ sequencer.c: int apply_autostash(const char *path)
       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)
     ++				    const char *label_ours, const char *label_theirs,
     ++				    const char *label_base)
       {
       	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 cha
       	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);
     ++				       label_ours, label_theirs, label_base);
       
       	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 cha
      +}
      +
      +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,
     -+				    const char *label1, const char *label2,
     -+				    const char *label_ancestor)
     ++				    const char *label_ours, const char *label_theirs,
     ++				    const char *label_base)
      +{
      +	return apply_save_autostash_ref(r, refname, 1,
     -+					label1, label2, label_ancestor);
     ++					label_ours, label_theirs, label_base);
       }
       
       static int checkout_onto(struct repository *r, struct replay_opts *opts,
     @@ sequencer.h: int save_autostash_ref(struct repository *r, const char *refname);
       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);
     ++				    const char *label_ours, const char *label_theirs,
     ++				    const char *label_base);
       
       #define SUMMARY_INITIAL_COMMIT   (1 << 0)
       #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
 4:  1c29e19971 ! 4:  aa18313362 checkout: -m (--merge) uses autostash when switching branches
     @@ Documentation/git-checkout.adoc: $ git checkout mytopic
      +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 the stash application results in conflicts, they are not
     -+resolved and the stash is saved to the stash list:
     ++switch.  If this process results in conflicts, a stash entry is saved
     ++and made available in `git stash list`:
       
       ------------
       $ git checkout -m mytopic
     @@ Documentation/git-checkout.adoc: $ git checkout mytopic
      
       ## Documentation/git-switch.adoc ##
      @@ Documentation/git-switch.adoc: variable.
     - 	If you have local modifications to one or more files that are
     - 	different between the current branch and the branch to which
     - 	you are switching, the command refuses to switch branches in
     + 
     + `-m`::
     + `--merge`::
     +-	If you have local modifications to one or more files that are
     +-	different between the current branch and the branch to which
     +-	you are switching, the command refuses to switch branches in
      -	order to preserve your modifications in context.  However,
      -	with this option, a three-way merge between the current
      -	branch, your working tree contents, and the new branch is
     @@ Documentation/git-switch.adoc: variable.
      -paths are left unmerged, and you need to resolve the conflicts
      -and mark the resolved paths with `git add` (or `git rm` if the merge
      -should result in deletion of the path).
     -+	order to preserve your modifications in context.  With this
     -+	option, the conflicting local changes are automatically
     -+	stashed before the switch and reapplied 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.  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.
     ++	If you have local modifications to one or more files that
     ++	are different between the current branch and the branch to
     ++	which you are switching, the command normally refuses to
     ++	switch branches in order to preserve your modifications in
     ++	context.  However, with this option, the conflicting local
     ++	changes are automatically stashed before the switch and
     ++	reapplied 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.  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
     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
      +	git clean -f &&
      +
      +	fill 0 x y z >same &&
     ++	git stash list >stash-before &&
      +	git checkout -m side >actual 2>&1 &&
      +	test_grep ! "Created autostash" actual &&
     ++	git stash list >stash-after &&
     ++	test_cmp stash-before stash-after &&
      +	fill 0 x y z >expect &&
      +	test_cmp expect same
      +'
     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
      +
      +	fill DIRTY a b c INSERTED d >file &&
      +
     ++	git stash list >stash-before &&
      +	git checkout -m nonoverlap_base 2>stderr &&
      +	test_grep "Applied autostash" stderr &&
      +	test_grep ! "resulted in conflicts" stderr &&
      +
     ++	git stash list >stash-after &&
     ++	test_cmp stash-before stash-after &&
     ++
      +	fill DIRTY a b c d >expect &&
      +	test_cmp expect file &&
      +

-- 
gitgitgadget