From: Harald Nordgren via GitGitGadget Date: Thu, 09 Apr 2026 19:17:11 GMT Subject: [PATCH v8 0/4] checkout: 'autostash' for branch switching Message-ID: In-Reply-To: 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=