{"thread":{"id":"65220","subject":"[PATCH] checkout: add --autostash option for branch switching","startedAt":"2026-03-12T13:26:41Z","lastAt":"2026-05-08T13:02:14Z","messageCount":164,"participants":["Harald Nordgren via GitGitGadget","Junio C Hamano","Harald Nordgren","Phillip Wood","Chris Torek","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"538745","messageId":"pull.2234.git.git.1773321998854.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":null,"subject":"[PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-12T13:26:38Z","receivedAt":"2026-03-12T13:26:41Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches, local modifications in the working tree can\nprevent the checkout from succeeding.  While \"git rebase\" and \"git\nmerge\" already support --autostash to handle this case automatically,\n\"git checkout\" and \"git switch\" require users to manually stash and\nunstash their changes.\n\nTeach \"git checkout\" and \"git switch\" to accept --autostash and\n--no-autostash options that automatically create a temporary stash\nentry before the branch switch begins and apply it after the switch\ncompletes.  If the stash application results in conflicts, the stash\nentry is saved to the stash list so the user can resolve them later.\n\nAlso add a checkout.autoStash configuration option that enables this\nbehavior by default, which can be overridden with --no-autostash on\nthe command line.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    checkout: 'autostash' for branch switching\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v1\nPull-Request: https://github.com/git/git/pull/2234\n\n Documentation/config/checkout.adoc |  12 ++\n Documentation/git-checkout.adoc    |   9 ++\n Documentation/git-switch.adoc      |   9 ++\n builtin/checkout.c                 |  16 +++\n t/meson.build                      |   1 +\n t/t2061-switch-autostash.sh        | 181 +++++++++++++++++++++++++++++\n t/t9902-completion.sh              |   1 +\n 7 files changed, 229 insertions(+)\n create mode 100755 t/t2061-switch-autostash.sh\n\ndiff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc\nindex e35d212969..2e157c5398 100644\n--- a/Documentation/config/checkout.adoc\n+++ b/Documentation/config/checkout.adoc\n@@ -36,6 +36,18 @@ with a small number of cores, the default sequential checkout often performs\n better. The size and compression level of a repository might also influence how\n well the parallel version performs.\n \n+`checkout.autoStash`::\n+\tWhen set to true, automatically create a temporary stash entry\n+\tbefore the operation begins, and apply it after the operation\n+\tends.  This means that you can run `git checkout` or `git switch`\n+\ton a dirty worktree.  However, use with care: the final stash\n+\tapplication after a successful branch switch might result in\n+\tnon-trivial conflicts.\n+\tThis option can be overridden by the `--no-autostash` and\n+\t`--autostash` options of linkgit:git-checkout[1] and\n+\tlinkgit:git-switch[1].\n+\tDefaults to false.\n+\n `checkout.thresholdForParallelism`::\n \tWhen running parallel checkout with a small number of files, the cost\n \tof subprocess spawning and inter-process communication might outweigh\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..96d9bf9203 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -272,6 +272,15 @@ When switching branches with `--merge`, staged changes may be lost.\n \t`merge.conflictStyle` configuration variable.  Possible values are\n \t`merge` (default), `diff3`, and `zdiff3`.\n \n+`--autostash`::\n+`--no-autostash`::\n+\tWhen switching branches, automatically create a temporary stash\n+\tentry before the operation begins, and apply it after the\n+\toperation ends.  This means that you can switch branches on a\n+\tdirty worktree.  However, use with care: the final stash\n+\tapplication after a successful branch switch might result in\n+\tnon-trivial conflicts.\n+\n `-p`::\n `--patch`::\n \tInteractively select hunks in the difference between the\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..b296df2a0b 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -142,6 +142,15 @@ should result in deletion of the path).\n \t`merge.conflictStyle` configuration variable.  Possible values are\n \t`merge` (default), `diff3`, and `zdiff3`.\n \n+`--autostash`::\n+`--no-autostash`::\n+\tAutomatically create a temporary stash entry before the\n+\toperation begins, and apply it after the operation ends.\n+\tThis means that you can switch branches on a dirty worktree.\n+\tHowever, use with care: the final stash application after a\n+\tsuccessful branch switch might result in non-trivial\n+\tconflicts.\n+\n `-q`::\n `--quiet`::\n \tQuiet, suppress feedback messages.\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1d1667fa4c..453dbe3230 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -30,6 +30,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -68,6 +69,7 @@ struct checkout_opts {\n \tint only_merge_on_switching_branches;\n \tint can_switch_when_in_progress;\n \tint orphan_from_empty_tree;\n+\tint autostash;\n \tint empty_pathspec_ok;\n \tint checkout_index;\n \tint checkout_worktree;\n@@ -1202,9 +1204,16 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (opts->autostash) {\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\t\tcreate_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n \t\t\tbranch_info_release(&old_branch_info);\n \t\t\treturn ret;\n \t\t}\n@@ -1215,6 +1224,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n \n@@ -1236,6 +1247,10 @@ static int git_checkout_config(const char *var, const char *value,\n \t\topts->dwim_new_local_branch = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"checkout.autostash\")) {\n+\t\topts->autostash = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n@@ -1745,6 +1760,7 @@ static struct option *add_common_switch_branch_options(\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"ignore-other-worktrees\", &opts->ignore_other_worktrees,\n \t\t\t N_(\"do not check if another worktree is using this branch\")),\n+\t\tOPT_AUTOSTASH(&opts->autostash),\n \t\tOPT_END()\n \t};\n \tstruct option *newopts = parse_options_concat(prevopts, options);\ndiff --git a/t/meson.build b/t/meson.build\nindex f66a73f8a0..0645253d25 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -275,6 +275,7 @@ integration_tests = [\n   't2030-unresolve-info.sh',\n   't2050-git-dir-relative.sh',\n   't2060-switch.sh',\n+  't2061-switch-autostash.sh',\n   't2070-restore.sh',\n   't2071-restore-patch.sh',\n   't2072-restore-pathspec-file.sh',\ndiff --git a/t/t2061-switch-autostash.sh b/t/t2061-switch-autostash.sh\nnew file mode 100755\nindex 0000000000..6409a2afbf\n--- /dev/null\n+++ b/t/t2061-switch-autostash.sh\n@@ -0,0 +1,181 @@\n+#!/bin/sh\n+\n+test_description='checkout/switch --autostash tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\techo file0content >file0 &&\n+\techo file1content >file1 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"initial commit\" &&\n+\tgit branch other-branch &&\n+\techo file1main >file1 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"modify file1 on main\" &&\n+\tgit checkout other-branch &&\n+\techo file1other >file1 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"modify file1 on other-branch\" &&\n+\techo file2content >file2 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"add file2 on other-branch\" &&\n+\tgit checkout main\n+'\n+\n+test_expect_success 'switch --autostash on dirty worktree' '\n+\tgit branch branch1 other-branch &&\n+\techo dirty >file0 &&\n+\tgit switch --autostash branch1 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'checkout --autostash on dirty worktree' '\n+\tgit branch branch2 other-branch &&\n+\techo dirty >file0 &&\n+\tgit checkout --autostash branch2 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit checkout main\n+'\n+\n+test_expect_success 'switch: checkout.autostash config' '\n+\tgit branch branch3 other-branch &&\n+\techo dirty >file0 &&\n+\ttest_config checkout.autostash true &&\n+\tgit switch branch3 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'checkout: checkout.autostash config' '\n+\tgit branch branch4 other-branch &&\n+\techo dirty >file0 &&\n+\ttest_config checkout.autostash true &&\n+\tgit checkout branch4 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit checkout main\n+'\n+\n+test_expect_success '--no-autostash overrides checkout.autostash' '\n+\tgit branch branch5 other-branch &&\n+\techo dirty >file1 &&\n+\ttest_config checkout.autostash true &&\n+\ttest_must_fail git switch --no-autostash branch5 2>stderr &&\n+\ttest_grep ! \"Created autostash\" stderr &&\n+\tgit checkout -- file1\n+'\n+\n+test_expect_success '--autostash overrides checkout.autostash=false' '\n+\tgit branch branch6 other-branch &&\n+\techo dirty >file0 &&\n+\ttest_config checkout.autostash false &&\n+\tgit switch --autostash branch6 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash with dirty index' '\n+\tgit branch branch7 other-branch &&\n+\techo dirty-index >file0 &&\n+\tgit add file0 &&\n+\tgit switch --autostash branch7 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty-index >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit checkout -- file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash bypasses conflicting local changes' '\n+\tgit branch branch8 other-branch &&\n+\techo dirty >file1 &&\n+\ttest_must_fail git switch branch8 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit switch --autostash branch8 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash is a no-op with clean worktree' '\n+\tgit branch branch9 other-branch &&\n+\tgit switch --autostash branch9 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit switch main\n+'\n+\n+test_expect_success '--autostash with --merge stashes and switches' '\n+\tgit branch branch10 other-branch &&\n+\techo dirty >file0 &&\n+\tgit switch --autostash --merge branch10 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash with staged conflicting changes' '\n+\tgit branch branch11 other-branch &&\n+\techo staged-change >file1 &&\n+\tgit add file1 &&\n+\tgit switch --autostash branch11 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard &&\n+\tgit switch main\n+'\n+\n+test_expect_success '--autostash with --force preserves dirty changes' '\n+\tgit branch branch12 other-branch &&\n+\techo dirty-force >file1 &&\n+\tgit switch --autostash --force branch12 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard &&\n+\tgit switch main\n+'\n+\n+test_expect_success '--autostash with new branch creation' '\n+\techo dirty >file0 &&\n+\tgit switch --autostash -c branch13 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main &&\n+\tgit branch -D branch13\n+'\n+\n+test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 2f9a597ec7..f33ca543a9 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2602,6 +2602,7 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n \t--auto-advance Z\n+\t--autostash Z\n \t--progress Z\n \t--guess Z\n \t--no-guess Z\n\nbase-commit: 7f19e4e1b6a3ad259e2ed66033e01e03b8b74c5e\n-- \ngitgitgadget\n"},{"id":"538752","messageId":"xmqqeclpi00y.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.git.git.1773321998854.gitgitgadget@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T14:40:29Z","receivedAt":"2026-03-12T14:40:32Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When switching branches, local modifications in the working tree can\n> prevent the checkout from succeeding.  While \"git rebase\" and \"git\n> merge\" already support --autostash to handle this case automatically,\n> \"git checkout\" and \"git switch\" require users to manually stash and\n> unstash their changes.\n>\n> Teach \"git checkout\" and \"git switch\" to accept --autostash and\n> --no-autostash options that automatically create a temporary stash\n> entry before the branch switch begins and apply it after the switch\n> completes.  If the stash application results in conflicts, the stash\n> entry is saved to the stash list so the user can resolve them later.\n>\n> Also add a checkout.autoStash configuration option that enables this\n> behavior by default, which can be overridden with --no-autostash on\n> the command line.\n\nUnconditionally always stash when checkout happens?  This feature as\nimplemented does not have to be a separate feature.  It can be done\nby end-users as a short-cut for \"stash\" followed by \"checkout\" via\nalias or custom command.\n\nPerhaps doing it this way would make it more worth doing?\n\n - At the beginning of branch switching, ask a new helper function\n   that takes the branch we are switching to as an argument this\n   question:\n\n   Do any paths that are different between the current branch and\n   the branch we are switching to have local (i.e., either in the\n   index or in the working tree) change [Yes/No]?\n\n - When the answer is \"yes\", save the local changes to a new stash\n   entry, and clear the local changes from the index and from the\n   working tree.  If not, do not bother with stash at all.\n\n - Switch to other branch the usual way.  This will never conflict.\n\n - If we created a stash entry earlier, try to unstash it.  It may\n   conflict or it may not.  \n\n   - If it does not conflict, then we are done.  We drop that stash\n     entry, and tell nothing about the stash to the user, as there\n     is nothing they can do to the now-consumed stash.\n\n   - If it does conflict, tell the user that the original change is\n     in the stash, and can be used to recover if you botch the\n     conflict resolution, and also tell the user that they need to\n     drop the stash entry once they are done with the change that\n     caused this current conflict.\n\nEssentially, the new \"autostaash only when needed\" would become a\nmuch better reimplementation of the \"-m\" option.  From the point of\nview of a user who is used to \"checkout -m\", the basic workflow\nwould be the same, only with vast improvement.\n\n - It may not conflict and merge cleanly, in which case they do not\n   have to do anything.  This is the same as status quo.\n\n - It may conflict and they find it too involved to resolve right at\n   the moment, in which case they now have a choiceto say \"git reset\n   --hard\", essentially declaring \"I prioritize working on this new\n   branch; I'll deal with the work in progress I started on the\n   previous branch later\", and then later they can \"git stash pop\"\n   to deal with it.\n\n   Which is a vast improvement over the current \"-m\" that gives you\n   only one chance to resolve it right.\n\n - It may conflict and they may be able to resolve cleanly, in which\n   case they have to remember that they need to do an extra thing\n   (i.e., drop the stash we created just in case) but that may not\n   be too bad a tradeoff.\n\nIf we can sell it as an improved implementation of \"-m\", we probably\ncan lose some code that the current \"-m\" implementation uses to do\nits merge; we'd be instead using the \"unstash\" code paths.\n\nAnd the new helper function to detect if switching from one commit\nto another commit would never conflict can later be used to enhance\n\"git rebase\" as well---we could call it N times to rebase a branch\nwith N commits and if all steps are clear, we do not have to insist\nthat there is no local changes like we do currently.\n\nHmm?\n"},{"id":"538800","messageId":"20260312193300.41693-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqeclpi00y.fsf@gitster.g","subject":"Re: [PATCH v31 0/2] status: add status.compareBranches config for multiple branch comparisons","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-03-12T19:33:00Z","receivedAt":"2026-03-12T19:33:04Z","isPatch":true,"body":"Good point! I have been running this today, and it gets a bit annoying when\nit's stashing and unstashing needlessly.\n\nI updated the code!\n\n\nHarald\n"},{"id":"538801","messageId":"pull.2234.v2.git.git.1773344022931.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.git.git.1773321998854.gitgitgadget@gmail.com","subject":"[PATCH v2] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-12T19:33:42Z","receivedAt":"2026-03-12T19:33:45Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches, local modifications in the working tree can\nprevent the checkout from succeeding.  While \"git rebase\" and \"git\nmerge\" already support --autostash to handle this case automatically,\n\"git checkout\" and \"git switch\" require users to manually stash and\nunstash their changes.\n\nTeach \"git checkout\" and \"git switch\" to accept --autostash and\n--no-autostash options that automatically create a temporary stash\nentry before the branch switch begins and apply it after the switch\ncompletes.  If the stash application results in conflicts, the stash\nentry is saved to the stash list so the user can resolve them later.\n\nAlso add a checkout.autoStash configuration option that enables this\nbehavior by default, which can be overridden with --no-autostash on\nthe command line.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    checkout: 'autostash' for branch switching\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v2\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v1:\n\n 1:  12194f6fbe ! 1:  be2b697c54 checkout: add --autostash option for branch switching\n     @@ Documentation/config/checkout.adoc: with a small number of cores, the default se\n       well the parallel version performs.\n       \n      +`checkout.autoStash`::\n     -+\tWhen set to true, automatically create a temporary stash entry\n     -+\tbefore the operation begins, and apply it after the operation\n     -+\tends.  This means that you can run `git checkout` or `git switch`\n     -+\ton a dirty worktree.  However, use with care: the final stash\n     -+\tapplication after a successful branch switch might result in\n     -+\tnon-trivial conflicts.\n     ++\tWhen set to true, enable autostash for `git checkout` and\n     ++\t`git switch` branch switching operations.  When a branch\n     ++\tswitch would fail because local changes (in the index or the\n     ++\tworking tree) overlap with paths that differ between the\n     ++\tcurrent and target branch, a temporary stash entry is\n     ++\tautomatically created before the switch and applied after\n     ++\tit completes.  If the local changes do not overlap with the\n     ++\tbranch difference, the switch proceeds normally without\n     ++\tstashing.\n      +\tThis option can be overridden by the `--no-autostash` and\n      +\t`--autostash` options of linkgit:git-checkout[1] and\n      +\tlinkgit:git-switch[1].\n     @@ Documentation/git-checkout.adoc: When switching branches with `--merge`, staged\n       \n      +`--autostash`::\n      +`--no-autostash`::\n     -+\tWhen switching branches, automatically create a temporary stash\n     -+\tentry before the operation begins, and apply it after the\n     -+\toperation ends.  This means that you can switch branches on a\n     -+\tdirty worktree.  However, use with care: the final stash\n     -+\tapplication after a successful branch switch might result in\n     -+\tnon-trivial conflicts.\n     ++\tWhen switching branches, if any of the paths that differ\n     ++\tbetween the current branch and the target branch have local\n     ++\tchanges (in the index or the working tree), automatically\n     ++\tcreate a temporary stash entry before the operation begins,\n     ++\tand apply it after the operation ends.  If the local changes\n     ++\tdo not overlap with the branch difference, the switch proceeds\n     ++\twithout stashing.  When a stash entry is created and the\n     ++\tsubsequent application results in conflicts, the stash entry\n     ++\tis saved so that you can use `git stash pop` to recover and\n     ++\t`git stash drop` when done.  Use with `--force` to always\n     ++\tstash local changes regardless of conflicts.\n      +\n       `-p`::\n       `--patch`::\n     @@ Documentation/git-switch.adoc: should result in deletion of the path).\n       \n      +`--autostash`::\n      +`--no-autostash`::\n     -+\tAutomatically create a temporary stash entry before the\n     -+\toperation begins, and apply it after the operation ends.\n     -+\tThis means that you can switch branches on a dirty worktree.\n     -+\tHowever, use with care: the final stash application after a\n     -+\tsuccessful branch switch might result in non-trivial\n     -+\tconflicts.\n     ++\tWhen switching branches, if any of the paths that differ\n     ++\tbetween the current branch and the target branch have local\n     ++\tchanges (in the index or the working tree), automatically\n     ++\tcreate a temporary stash entry before the operation begins,\n     ++\tand apply it after the operation ends.  If the local changes\n     ++\tdo not overlap with the branch difference, the switch proceeds\n     ++\twithout stashing.  When a stash entry is created and the\n     ++\tsubsequent application results in conflicts, the stash entry\n     ++\tis saved so that you can use `git stash pop` to recover and\n     ++\t`git stash drop` when done.  Use with `--force` to always\n     ++\tstash local changes regardless of conflicts.\n      +\n       `-q`::\n       `--quiet`::\n     @@ builtin/checkout.c: struct checkout_opts {\n       \tint empty_pathspec_ok;\n       \tint checkout_index;\n       \tint checkout_worktree;\n     +@@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n     + \trelease_revisions(&revs);\n     + }\n     + \n     ++static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n     ++\t\t\t\t\t  struct branch_info *new_branch_info)\n     ++{\n     ++\tstruct tree_desc trees[2];\n     ++\tstruct tree *old_tree, *new_tree;\n     ++\tstruct unpack_trees_options topts;\n     ++\tstruct index_state tmp_index = INDEX_STATE_INIT(the_repository);\n     ++\tconst struct object_id *old_commit_oid;\n     ++\tint ret;\n     ++\n     ++\tif (!new_branch_info->commit)\n     ++\t\treturn 0;\n     ++\n     ++\told_commit_oid = old_branch_info->commit ?\n     ++\t\t&old_branch_info->commit->object.oid :\n     ++\t\tthe_hash_algo->empty_tree;\n     ++\told_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);\n     ++\tif (!old_tree)\n     ++\t\treturn 0;\n     ++\n     ++\tnew_tree = repo_get_commit_tree(the_repository,\n     ++\t\t\t\t\tnew_branch_info->commit);\n     ++\tif (!new_tree)\n     ++\t\treturn 0;\n     ++\tif (repo_parse_tree(the_repository, new_tree) < 0)\n     ++\t\treturn 0;\n     ++\n     ++\tmemset(&topts, 0, sizeof(topts));\n     ++\ttopts.head_idx = -1;\n     ++\ttopts.src_index = the_repository->index;\n     ++\ttopts.dst_index = &tmp_index;\n     ++\ttopts.initial_checkout = is_index_unborn(the_repository->index);\n     ++\ttopts.merge = 1;\n     ++\ttopts.update = 1;\n     ++\ttopts.dry_run = 1;\n     ++\ttopts.quiet = 1;\n     ++\ttopts.fn = twoway_merge;\n     ++\n     ++\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n     ++\t\t       old_tree->buffer, old_tree->size);\n     ++\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n     ++\t\t       new_tree->buffer, new_tree->size);\n     ++\n     ++\tret = unpack_trees(2, trees, &topts);\n     ++\tdiscard_index(&tmp_index);\n     ++\n     ++\treturn ret != 0;\n     ++}\n     ++\n     + static int switch_branches(const struct checkout_opts *opts,\n     + \t\t\t   struct branch_info *new_branch_info)\n     + {\n      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \t\t\tdo_merge = 0;\n       \t}\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\tif (opts->autostash) {\n      +\t\tif (repo_read_index(the_repository) < 0)\n      +\t\t\tdie(_(\"index file corrupt\"));\n     -+\t\tcreate_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n     ++\t\tif (opts->discard_changes ||\n     ++\t\t    checkout_would_clobber_changes(&old_branch_info,\n     ++\t\t\t\t\t\t   new_branch_info))\n     ++\t\t\tcreate_autostash_ref(the_repository,\n     ++\t\t\t\t\t     \"CHECKOUT_AUTOSTASH\");\n      +\t}\n      +\n       \tif (do_merge) {\n     @@ t/t2061-switch-autostash.sh (new)\n      +\tgit checkout main\n      +'\n      +\n     -+test_expect_success 'switch --autostash on dirty worktree' '\n     ++test_expect_success 'switch --autostash skips stash when no conflict' '\n      +\tgit branch branch1 other-branch &&\n      +\techo dirty >file0 &&\n      +\tgit switch --autostash branch1 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit switch main\n      +'\n      +\n     -+test_expect_success 'checkout --autostash on dirty worktree' '\n     ++test_expect_success 'checkout --autostash skips stash when no conflict' '\n      +\tgit branch branch2 other-branch &&\n      +\techo dirty >file0 &&\n      +\tgit checkout --autostash branch2 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit checkout main\n      +'\n      +\n     -+test_expect_success 'switch: checkout.autostash config' '\n     ++test_expect_success 'switch: checkout.autostash config skips stash when no conflict' '\n      +\tgit branch branch3 other-branch &&\n      +\techo dirty >file0 &&\n      +\ttest_config checkout.autostash true &&\n      +\tgit switch branch3 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit switch main\n      +'\n      +\n     -+test_expect_success 'checkout: checkout.autostash config' '\n     ++test_expect_success 'checkout: checkout.autostash config skips stash when no conflict' '\n      +\tgit branch branch4 other-branch &&\n      +\techo dirty >file0 &&\n      +\ttest_config checkout.autostash true &&\n      +\tgit checkout branch4 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit checkout main\n     @@ t/t2061-switch-autostash.sh (new)\n      +\techo dirty >file0 &&\n      +\ttest_config checkout.autostash false &&\n      +\tgit switch --autostash branch6 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit switch main\n      +'\n      +\n     -+test_expect_success 'autostash with dirty index' '\n     ++test_expect_success 'autostash with non-conflicting dirty index' '\n      +\tgit branch branch7 other-branch &&\n      +\techo dirty-index >file0 &&\n      +\tgit add file0 &&\n      +\tgit switch --autostash branch7 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty-index >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit checkout -- file0 &&\n     @@ t/t2061-switch-autostash.sh (new)\n      +\tgit switch main\n      +'\n      +\n     -+test_expect_success '--autostash with --merge stashes and switches' '\n     ++test_expect_success '--autostash with --merge skips stash when no conflict' '\n      +\tgit branch branch10 other-branch &&\n      +\techo dirty >file0 &&\n      +\tgit switch --autostash --merge branch10 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit switch main\n     @@ t/t2061-switch-autostash.sh (new)\n      +\tgit switch main\n      +'\n      +\n     -+test_expect_success '--autostash with new branch creation' '\n     ++test_expect_success '--autostash with new branch creation skips stash' '\n      +\techo dirty >file0 &&\n      +\tgit switch --autostash -c branch13 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\techo dirty >expected &&\n      +\ttest_cmp expected file0 &&\n      +\tgit switch main &&\n      +\tgit branch -D branch13\n      +'\n      +\n     ++test_expect_success 'autostash with conflicting changes that apply cleanly' '\n     ++\tgit branch branch14 other-branch &&\n     ++\techo file1other >file1 &&\n     ++\tgit switch --autostash branch14 >actual 2>&1 &&\n     ++\ttest_grep \"Created autostash\" actual &&\n     ++\ttest_grep \"Applied autostash\" actual &&\n     ++\techo file1other >expected &&\n     ++\ttest_cmp expected file1 &&\n     ++\tgit switch main\n     ++'\n     ++\n      +test_done\n      \n       ## t/t9902-completion.sh ##\n\n\n Documentation/config/checkout.adoc |  15 +++\n Documentation/git-checkout.adoc    |  14 +++\n Documentation/git-switch.adoc      |  14 +++\n builtin/checkout.c                 |  69 +++++++++++\n t/meson.build                      |   1 +\n t/t2061-switch-autostash.sh        | 184 +++++++++++++++++++++++++++++\n t/t9902-completion.sh              |   1 +\n 7 files changed, 298 insertions(+)\n create mode 100755 t/t2061-switch-autostash.sh\n\ndiff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc\nindex e35d212969..268b7b1b8b 100644\n--- a/Documentation/config/checkout.adoc\n+++ b/Documentation/config/checkout.adoc\n@@ -36,6 +36,21 @@ with a small number of cores, the default sequential checkout often performs\n better. The size and compression level of a repository might also influence how\n well the parallel version performs.\n \n+`checkout.autoStash`::\n+\tWhen set to true, enable autostash for `git checkout` and\n+\t`git switch` branch switching operations.  When a branch\n+\tswitch would fail because local changes (in the index or the\n+\tworking tree) overlap with paths that differ between the\n+\tcurrent and target branch, a temporary stash entry is\n+\tautomatically created before the switch and applied after\n+\tit completes.  If the local changes do not overlap with the\n+\tbranch difference, the switch proceeds normally without\n+\tstashing.\n+\tThis option can be overridden by the `--no-autostash` and\n+\t`--autostash` options of linkgit:git-checkout[1] and\n+\tlinkgit:git-switch[1].\n+\tDefaults to false.\n+\n `checkout.thresholdForParallelism`::\n \tWhen running parallel checkout with a small number of files, the cost\n \tof subprocess spawning and inter-process communication might outweigh\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..19da63c5be 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -272,6 +272,20 @@ When switching branches with `--merge`, staged changes may be lost.\n \t`merge.conflictStyle` configuration variable.  Possible values are\n \t`merge` (default), `diff3`, and `zdiff3`.\n \n+`--autostash`::\n+`--no-autostash`::\n+\tWhen switching branches, if any of the paths that differ\n+\tbetween the current branch and the target branch have local\n+\tchanges (in the index or the working tree), automatically\n+\tcreate a temporary stash entry before the operation begins,\n+\tand apply it after the operation ends.  If the local changes\n+\tdo not overlap with the branch difference, the switch proceeds\n+\twithout stashing.  When a stash entry is created and the\n+\tsubsequent application results in conflicts, the stash entry\n+\tis saved so that you can use `git stash pop` to recover and\n+\t`git stash drop` when done.  Use with `--force` to always\n+\tstash local changes regardless of conflicts.\n+\n `-p`::\n `--patch`::\n \tInteractively select hunks in the difference between the\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..6028479fef 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -142,6 +142,20 @@ should result in deletion of the path).\n \t`merge.conflictStyle` configuration variable.  Possible values are\n \t`merge` (default), `diff3`, and `zdiff3`.\n \n+`--autostash`::\n+`--no-autostash`::\n+\tWhen switching branches, if any of the paths that differ\n+\tbetween the current branch and the target branch have local\n+\tchanges (in the index or the working tree), automatically\n+\tcreate a temporary stash entry before the operation begins,\n+\tand apply it after the operation ends.  If the local changes\n+\tdo not overlap with the branch difference, the switch proceeds\n+\twithout stashing.  When a stash entry is created and the\n+\tsubsequent application results in conflicts, the stash entry\n+\tis saved so that you can use `git stash pop` to recover and\n+\t`git stash drop` when done.  Use with `--force` to always\n+\tstash local changes regardless of conflicts.\n+\n `-q`::\n `--quiet`::\n \tQuiet, suppress feedback messages.\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1d1667fa4c..4a251a8732 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -30,6 +30,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -68,6 +69,7 @@ struct checkout_opts {\n \tint only_merge_on_switching_branches;\n \tint can_switch_when_in_progress;\n \tint orphan_from_empty_tree;\n+\tint autostash;\n \tint empty_pathspec_ok;\n \tint checkout_index;\n \tint checkout_worktree;\n@@ -1157,6 +1159,55 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n \trelease_revisions(&revs);\n }\n \n+static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n+\t\t\t\t\t  struct branch_info *new_branch_info)\n+{\n+\tstruct tree_desc trees[2];\n+\tstruct tree *old_tree, *new_tree;\n+\tstruct unpack_trees_options topts;\n+\tstruct index_state tmp_index = INDEX_STATE_INIT(the_repository);\n+\tconst struct object_id *old_commit_oid;\n+\tint ret;\n+\n+\tif (!new_branch_info->commit)\n+\t\treturn 0;\n+\n+\told_commit_oid = old_branch_info->commit ?\n+\t\t&old_branch_info->commit->object.oid :\n+\t\tthe_hash_algo->empty_tree;\n+\told_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);\n+\tif (!old_tree)\n+\t\treturn 0;\n+\n+\tnew_tree = repo_get_commit_tree(the_repository,\n+\t\t\t\t\tnew_branch_info->commit);\n+\tif (!new_tree)\n+\t\treturn 0;\n+\tif (repo_parse_tree(the_repository, new_tree) < 0)\n+\t\treturn 0;\n+\n+\tmemset(&topts, 0, sizeof(topts));\n+\ttopts.head_idx = -1;\n+\ttopts.src_index = the_repository->index;\n+\ttopts.dst_index = &tmp_index;\n+\ttopts.initial_checkout = is_index_unborn(the_repository->index);\n+\ttopts.merge = 1;\n+\ttopts.update = 1;\n+\ttopts.dry_run = 1;\n+\ttopts.quiet = 1;\n+\ttopts.fn = twoway_merge;\n+\n+\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n+\t\t       old_tree->buffer, old_tree->size);\n+\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n+\t\t       new_tree->buffer, new_tree->size);\n+\n+\tret = unpack_trees(2, trees, &topts);\n+\tdiscard_index(&tmp_index);\n+\n+\treturn ret != 0;\n+}\n+\n static int switch_branches(const struct checkout_opts *opts,\n \t\t\t   struct branch_info *new_branch_info)\n {\n@@ -1202,9 +1253,20 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (opts->autostash) {\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\t\tif (opts->discard_changes ||\n+\t\t    checkout_would_clobber_changes(&old_branch_info,\n+\t\t\t\t\t\t   new_branch_info))\n+\t\t\tcreate_autostash_ref(the_repository,\n+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH\");\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n \t\t\tbranch_info_release(&old_branch_info);\n \t\t\treturn ret;\n \t\t}\n@@ -1215,6 +1277,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n \n@@ -1236,6 +1300,10 @@ static int git_checkout_config(const char *var, const char *value,\n \t\topts->dwim_new_local_branch = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"checkout.autostash\")) {\n+\t\topts->autostash = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n \n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n@@ -1745,6 +1813,7 @@ static struct option *add_common_switch_branch_options(\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"ignore-other-worktrees\", &opts->ignore_other_worktrees,\n \t\t\t N_(\"do not check if another worktree is using this branch\")),\n+\t\tOPT_AUTOSTASH(&opts->autostash),\n \t\tOPT_END()\n \t};\n \tstruct option *newopts = parse_options_concat(prevopts, options);\ndiff --git a/t/meson.build b/t/meson.build\nindex f66a73f8a0..0645253d25 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -275,6 +275,7 @@ integration_tests = [\n   't2030-unresolve-info.sh',\n   't2050-git-dir-relative.sh',\n   't2060-switch.sh',\n+  't2061-switch-autostash.sh',\n   't2070-restore.sh',\n   't2071-restore-patch.sh',\n   't2072-restore-pathspec-file.sh',\ndiff --git a/t/t2061-switch-autostash.sh b/t/t2061-switch-autostash.sh\nnew file mode 100755\nindex 0000000000..d37d6f4b8b\n--- /dev/null\n+++ b/t/t2061-switch-autostash.sh\n@@ -0,0 +1,184 @@\n+#!/bin/sh\n+\n+test_description='checkout/switch --autostash tests'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\techo file0content >file0 &&\n+\techo file1content >file1 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"initial commit\" &&\n+\tgit branch other-branch &&\n+\techo file1main >file1 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"modify file1 on main\" &&\n+\tgit checkout other-branch &&\n+\techo file1other >file1 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"modify file1 on other-branch\" &&\n+\techo file2content >file2 &&\n+\tgit add . &&\n+\ttest_tick &&\n+\tgit commit -m \"add file2 on other-branch\" &&\n+\tgit checkout main\n+'\n+\n+test_expect_success 'switch --autostash skips stash when no conflict' '\n+\tgit branch branch1 other-branch &&\n+\techo dirty >file0 &&\n+\tgit switch --autostash branch1 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'checkout --autostash skips stash when no conflict' '\n+\tgit branch branch2 other-branch &&\n+\techo dirty >file0 &&\n+\tgit checkout --autostash branch2 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit checkout main\n+'\n+\n+test_expect_success 'switch: checkout.autostash config skips stash when no conflict' '\n+\tgit branch branch3 other-branch &&\n+\techo dirty >file0 &&\n+\ttest_config checkout.autostash true &&\n+\tgit switch branch3 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'checkout: checkout.autostash config skips stash when no conflict' '\n+\tgit branch branch4 other-branch &&\n+\techo dirty >file0 &&\n+\ttest_config checkout.autostash true &&\n+\tgit checkout branch4 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit checkout main\n+'\n+\n+test_expect_success '--no-autostash overrides checkout.autostash' '\n+\tgit branch branch5 other-branch &&\n+\techo dirty >file1 &&\n+\ttest_config checkout.autostash true &&\n+\ttest_must_fail git switch --no-autostash branch5 2>stderr &&\n+\ttest_grep ! \"Created autostash\" stderr &&\n+\tgit checkout -- file1\n+'\n+\n+test_expect_success '--autostash overrides checkout.autostash=false' '\n+\tgit branch branch6 other-branch &&\n+\techo dirty >file0 &&\n+\ttest_config checkout.autostash false &&\n+\tgit switch --autostash branch6 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash with non-conflicting dirty index' '\n+\tgit branch branch7 other-branch &&\n+\techo dirty-index >file0 &&\n+\tgit add file0 &&\n+\tgit switch --autostash branch7 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty-index >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit checkout -- file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash bypasses conflicting local changes' '\n+\tgit branch branch8 other-branch &&\n+\techo dirty >file1 &&\n+\ttest_must_fail git switch branch8 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit switch --autostash branch8 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash is a no-op with clean worktree' '\n+\tgit branch branch9 other-branch &&\n+\tgit switch --autostash branch9 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit switch main\n+'\n+\n+test_expect_success '--autostash with --merge skips stash when no conflict' '\n+\tgit branch branch10 other-branch &&\n+\techo dirty >file0 &&\n+\tgit switch --autostash --merge branch10 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main\n+'\n+\n+test_expect_success 'autostash with staged conflicting changes' '\n+\tgit branch branch11 other-branch &&\n+\techo staged-change >file1 &&\n+\tgit add file1 &&\n+\tgit switch --autostash branch11 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard &&\n+\tgit switch main\n+'\n+\n+test_expect_success '--autostash with --force preserves dirty changes' '\n+\tgit branch branch12 other-branch &&\n+\techo dirty-force >file1 &&\n+\tgit switch --autostash --force branch12 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard &&\n+\tgit switch main\n+'\n+\n+test_expect_success '--autostash with new branch creation skips stash' '\n+\techo dirty >file0 &&\n+\tgit switch --autostash -c branch13 >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\techo dirty >expected &&\n+\ttest_cmp expected file0 &&\n+\tgit switch main &&\n+\tgit branch -D branch13\n+'\n+\n+test_expect_success 'autostash with conflicting changes that apply cleanly' '\n+\tgit branch branch14 other-branch &&\n+\techo file1other >file1 &&\n+\tgit switch --autostash branch14 >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\techo file1other >expected &&\n+\ttest_cmp expected file1 &&\n+\tgit switch main\n+'\n+\n+test_done\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 2f9a597ec7..f33ca543a9 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2602,6 +2602,7 @@ test_expect_success 'double dash \"git checkout\"' '\n \t--ignore-other-worktrees Z\n \t--recurse-submodules Z\n \t--auto-advance Z\n+\t--autostash Z\n \t--progress Z\n \t--guess Z\n \t--no-guess Z\n\nbase-commit: 7f19e4e1b6a3ad259e2ed66033e01e03b8b74c5e\n-- \ngitgitgadget\n"},{"id":"538803","messageId":"xmqqjyvgesjx.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v2.git.git.1773344022931.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T19:50:10Z","receivedAt":"2026-03-12T19:50:13Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When switching branches, local modifications in the working tree can\n> prevent the checkout from succeeding.  While \"git rebase\" and \"git\n> merge\" already support --autostash to handle this case automatically,\n> \"git checkout\" and \"git switch\" require users to manually stash and\n> unstash their changes.\n>\n> Teach \"git checkout\" and \"git switch\" to accept --autostash and\n> --no-autostash options that automatically create a temporary stash\n> entry before the branch switch begins and apply it after the switch\n> completes.  If the stash application results in conflicts, the stash\n> entry is saved to the stash list so the user can resolve them later.\n>\n> Also add a checkout.autoStash configuration option that enables this\n> behavior by default, which can be overridden with --no-autostash on\n> the command line.\n\nWith this, shouldn't \"-m\" become a synonym for \"--autostash\"?\nFor users of \"checkout -m\", this is a strictly improved version of\nthe same feature, it seems.\n\nAlso, \"stash\" is merely an implementation detail of how we make the\nmerge safer, so from end-user's point of view, this feature is more\nlike \"switch to the other branch, while merging the local changes\nthere\", so calling it \"--merge\" or something may be much better than\ncalling it \"--autostash\".\n\nOther than that, I like the implementation in general.\n"},{"id":"538867","messageId":"20260313092258.30682-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqjyvgesjx.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-03-13T09:22:58Z","receivedAt":"2026-03-13T09:23:01Z","isPatch":true,"body":"> With this, shouldn't \"-m\" become a synonym for \"--autostash\"?\n> For users of \"checkout -m\", this is a strictly improved version of\n> the same feature, it seems.\n>\n> Also, \"stash\" is merely an implementation detail of how we make the\n> merge safer, so from end-user's point of view, this feature is more\n> like \"switch to the other branch, while merging the local changes\n> there\", so calling it \"--merge\" or something may be much better than\n> calling it \"--autostash\".\n\nThat's an interesting idea and I gave it a shot! I do worry about breaking\nthe existing '-m' flow because I don't understand its fundamentals.\n\nBut tests old seem to be passing (well, they did break and then I fixed the\ncode to make them pass again), so that's promising. Although hard for me to\nsay if the old test coverage protects against all possible regressions.\n\n> Other than that, I like the implementation in general.\n\nThanks for all your support, Junio!\n\n\nHarald\n"},{"id":"538868","messageId":"pull.2234.v3.git.git.1773393818235.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v2.git.git.1773344022931.gitgitgadget@gmail.com","subject":"[PATCH v3] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-13T09:23:38Z","receivedAt":"2026-03-13T09:23:41Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches, local modifications in the working tree can\nprevent the checkout from succeeding.  While \"git rebase\" and \"git\nmerge\" already support --autostash to handle this case automatically,\n\"git checkout\" and \"git switch\" require users to manually stash and\nunstash their changes.\n\nTeach \"git checkout\" and \"git switch\" to accept --autostash and\n--no-autostash options that automatically create a temporary stash\nentry before the branch switch begins and apply it after the switch\ncompletes.  If the stash application results in conflicts, the stash\nentry is saved to the stash list so the user can resolve them later.\n\nAlso add a checkout.autoStash configuration option that enables this\nbehavior by default, which can be overridden with --no-autostash on\nthe command line.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    checkout: 'autostash' for branch switching\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v3\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v2:\n\n 1:  be2b697c54 ! 1:  05f1e53163 checkout: add --autostash option for branch switching\n     @@ Commit message\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     - ## Documentation/config/checkout.adoc ##\n     -@@ Documentation/config/checkout.adoc: with a small number of cores, the default sequential checkout often performs\n     - better. The size and compression level of a repository might also influence how\n     - well the parallel version performs.\n     - \n     -+`checkout.autoStash`::\n     -+\tWhen set to true, enable autostash for `git checkout` and\n     -+\t`git switch` branch switching operations.  When a branch\n     -+\tswitch would fail because local changes (in the index or the\n     -+\tworking tree) overlap with paths that differ between the\n     -+\tcurrent and target branch, a temporary stash entry is\n     -+\tautomatically created before the switch and applied after\n     -+\tit completes.  If the local changes do not overlap with the\n     -+\tbranch difference, the switch proceeds normally without\n     -+\tstashing.\n     -+\tThis option can be overridden by the `--no-autostash` and\n     -+\t`--autostash` options of linkgit:git-checkout[1] and\n     -+\tlinkgit:git-switch[1].\n     -+\tDefaults to false.\n     -+\n     - `checkout.thresholdForParallelism`::\n     - \tWhen running parallel checkout with a small number of files, the cost\n     - \tof subprocess spawning and inter-process communication might outweigh\n     -\n       ## Documentation/git-checkout.adoc ##\n     -@@ Documentation/git-checkout.adoc: When switching branches with `--merge`, staged changes may be lost.\n     - \t`merge.conflictStyle` configuration variable.  Possible values are\n     - \t`merge` (default), `diff3`, and `zdiff3`.\n     +@@ Documentation/git-checkout.adoc: working tree, by copying them from elsewhere, extracting a tarball, etc.\n     + \tare different between the current branch and the branch to\n     + \twhich you are switching, the command refuses to switch\n     + \tbranches in order to preserve your modifications in context.\n     +-\tHowever, with this option, a three-way merge between the current\n     +-\tbranch, your working tree contents, and the new branch\n     +-\tis done, and you will be on the new branch.\n     +-+\n     +-When a merge conflict happens, the index entries for conflicting\n     +-paths are left unmerged, and you need to resolve the conflicts\n     +-and mark the resolved paths with `git add` (or `git rm` if the merge\n     +-should result in deletion of the path).\n     ++\tWith this option, the conflicting local changes are\n     ++\tautomatically stashed before the switch and reapplied\n     ++\tafterwards.  If the local changes do not overlap with the\n     ++\tdifferences between branches, the switch proceeds without\n     ++\tstashing.  If reapplying the stash results in conflicts, the\n     ++\tentry is saved to the stash list so you can use `git stash\n     ++\tpop` to recover and `git stash drop` when done.\n     + +\n     + When checking out paths from the index, this option lets you recreate\n     + the conflicted merge in the specified paths.  This option cannot be\n     + used when checking out paths from a tree-ish.\n     +-+\n     +-When switching branches with `--merge`, staged changes may be lost.\n     + \n     + `--conflict=<style>`::\n     + \tThe same as `--merge` option above, but changes the way the\n     +@@ Documentation/git-checkout.adoc: $ git checkout mytopic\n     + error: You have local changes to 'frotz'; not switching branches.\n     + ------------\n     + \n     +-You can give the `-m` flag to the command, which would try a\n     +-three-way merge:\n     ++You can give the `-m` flag to the command, which would save the local\n     ++changes in a stash entry and reset the working tree to allow switching:\n     + \n     + ------------\n     + $ git checkout -m mytopic\n     +-Auto-merging frotz\n     ++Created autostash: 7a9afa3\n     ++Applied autostash.\n     + ------------\n     + \n     +-After this three-way merge, the local modifications are _not_\n     ++After the switch, the local modifications are reapplied and are _not_\n     + registered in your index file, so `git diff` would show you what\n     + changes you made since the tip of the new branch.\n     + \n     + === 3. Merge conflict\n     + \n     +-When a merge conflict happens during switching branches with\n     +-the `-m` option, you would see something like this:\n     ++When the locally modified files overlap with files that need to be\n     ++updated by the branch switch, the changes are stashed and reapplied\n     ++after the switch.  If the stash application results in conflicts,\n     ++they are not resolved and the stash is saved to the stash list:\n     + \n     + ------------\n     + $ git checkout -m mytopic\n     +-Auto-merging frotz\n     +-ERROR: Merge conflict in frotz\n     +-fatal: merge program failed\n     ++Created autostash: 7a9afa3\n     ++Applying autostash resulted in conflicts.\n     ++Your changes are safe in the stash.\n     ++You can run \"git stash pop\" or \"git stash drop\" at any time.\n     + ------------\n       \n     -+`--autostash`::\n     -+`--no-autostash`::\n     -+\tWhen switching branches, if any of the paths that differ\n     -+\tbetween the current branch and the target branch have local\n     -+\tchanges (in the index or the working tree), automatically\n     -+\tcreate a temporary stash entry before the operation begins,\n     -+\tand apply it after the operation ends.  If the local changes\n     -+\tdo not overlap with the branch difference, the switch proceeds\n     -+\twithout stashing.  When a stash entry is created and the\n     -+\tsubsequent application results in conflicts, the stash entry\n     -+\tis saved so that you can use `git stash pop` to recover and\n     -+\t`git stash drop` when done.  Use with `--force` to always\n     -+\tstash local changes regardless of conflicts.\n     -+\n     - `-p`::\n     - `--patch`::\n     - \tInteractively select hunks in the difference between the\n     +-At this point, `git diff` shows the changes cleanly merged as in\n     +-the previous example, as well as the changes in the conflicted\n     +-files.  Edit and resolve the conflict and mark it resolved with\n     +-`git add` as usual:\n     +-\n     +-------------\n     +-$ edit frotz\n     +-$ git add frotz\n     +-------------\n     ++At this point, `git stash pop` can be used to recover and resolve\n     ++the conflicts, and `git stash drop` to discard the stash when done.\n     + \n     + CONFIGURATION\n     + -------------\n      \n       ## Documentation/git-switch.adoc ##\n     -@@ Documentation/git-switch.adoc: should result in deletion of the path).\n     - \t`merge.conflictStyle` configuration variable.  Possible values are\n     - \t`merge` (default), `diff3`, and `zdiff3`.\n     +@@ Documentation/git-switch.adoc: variable.\n     + \tIf you have local modifications to one or more files that are\n     + \tdifferent between the current branch and the branch to which\n     + \tyou are switching, the command refuses to switch branches in\n     +-\torder to preserve your modifications in context.  However,\n     +-\twith this option, a three-way merge between the current\n     +-\tbranch, your working tree contents, and the new branch is\n     +-\tdone, and you will be on the new branch.\n     +-+\n     +-When a merge conflict happens, the index entries for conflicting\n     +-paths are left unmerged, and you need to resolve the conflicts\n     +-and mark the resolved paths with `git add` (or `git rm` if the merge\n     +-should result in deletion of the path).\n     ++\torder to preserve your modifications in context.  With this\n     ++\toption, the conflicting local changes are automatically\n     ++\tstashed before the switch and reapplied afterwards.  If the\n     ++\tlocal changes do not overlap with the differences between\n     ++\tbranches, the switch proceeds without stashing.  If\n     ++\treapplying the stash results in conflicts, the entry is\n     ++\tsaved to the stash list so you can use `git stash pop` to\n     ++\trecover and `git stash drop` when done.\n     + \n     + `--conflict=<style>`::\n     + \tThe same as `--merge` option above, but changes the way the\n     +@@ Documentation/git-switch.adoc: $ git switch mytopic\n     + error: You have local changes to 'frotz'; not switching branches.\n     + ------------\n     + \n     +-You can give the `-m` flag to the command, which would try a three-way\n     +-merge:\n     ++You can give the `-m` flag to the command, which would save the local\n     ++changes in a stash entry and reset the working tree to allow switching:\n     + \n     + ------------\n     + $ git switch -m mytopic\n     +-Auto-merging frotz\n     ++Created autostash: 7a9afa3\n     ++Applied autostash.\n     + ------------\n     + \n     +-After this three-way merge, the local modifications are _not_\n     ++After the switch, the local modifications are reapplied and are _not_\n     + registered in your index file, so `git diff` would show you what\n     + changes you made since the tip of the new branch.\n       \n     -+`--autostash`::\n     -+`--no-autostash`::\n     -+\tWhen switching branches, if any of the paths that differ\n     -+\tbetween the current branch and the target branch have local\n     -+\tchanges (in the index or the working tree), automatically\n     -+\tcreate a temporary stash entry before the operation begins,\n     -+\tand apply it after the operation ends.  If the local changes\n     -+\tdo not overlap with the branch difference, the switch proceeds\n     -+\twithout stashing.  When a stash entry is created and the\n     -+\tsubsequent application results in conflicts, the stash entry\n     -+\tis saved so that you can use `git stash pop` to recover and\n     -+\t`git stash drop` when done.  Use with `--force` to always\n     -+\tstash local changes regardless of conflicts.\n     -+\n     - `-q`::\n     - `--quiet`::\n     - \tQuiet, suppress feedback messages.\n      \n       ## builtin/checkout.c ##\n     +@@\n     + #include \"merge-ll.h\"\n     + #include \"lockfile.h\"\n     + #include \"mem-pool.h\"\n     +-#include \"merge-ort-wrappers.h\"\n     + #include \"object-file.h\"\n     + #include \"object-name.h\"\n     + #include \"odb.h\"\n      @@\n       #include \"repo-settings.h\"\n       #include \"resolve-undo.h\"\n     @@ builtin/checkout.c\n       #include \"setup.h\"\n       #include \"submodule.h\"\n       #include \"symlinks.h\"\n     -@@ builtin/checkout.c: struct checkout_opts {\n     - \tint only_merge_on_switching_branches;\n     - \tint can_switch_when_in_progress;\n     - \tint orphan_from_empty_tree;\n     -+\tint autostash;\n     - \tint empty_pathspec_ok;\n     - \tint checkout_index;\n     - \tint checkout_worktree;\n     +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \n     + \t\tret = unpack_trees(2, trees, &topts);\n     + \t\tclear_unpack_trees_porcelain(&topts);\n     +-\t\tif (ret == -1) {\n     +-\t\t\t/*\n     +-\t\t\t * Unpack couldn't do a trivial merge; either\n     +-\t\t\t * give up or do a real merge, depending on\n     +-\t\t\t * whether the merge flag was used.\n     +-\t\t\t */\n     +-\t\t\tstruct tree *work;\n     +-\t\t\tstruct tree *old_tree;\n     +-\t\t\tstruct merge_options o;\n     +-\t\t\tstruct strbuf sb = STRBUF_INIT;\n     +-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n     +-\n     +-\t\t\tif (!opts->merge)\n     +-\t\t\t\treturn 1;\n     +-\n     +-\t\t\t/*\n     +-\t\t\t * Without old_branch_info->commit, the below is the same as\n     +-\t\t\t * the two-tree unpack we already tried and failed.\n     +-\t\t\t */\n     +-\t\t\tif (!old_branch_info->commit)\n     +-\t\t\t\treturn 1;\n     +-\t\t\told_tree = repo_get_commit_tree(the_repository,\n     +-\t\t\t\t\t\t\told_branch_info->commit);\n     +-\n     +-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n     +-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n     +-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n     +-\t\t\tstrbuf_release(&sb);\n     +-\n     +-\t\t\t/* Do more real merge */\n     +-\n     +-\t\t\t/*\n     +-\t\t\t * We update the index fully, then write the\n     +-\t\t\t * tree from the index, then merge the new\n     +-\t\t\t * branch with the current tree, with the old\n     +-\t\t\t * branch as the base. Then we reset the index\n     +-\t\t\t * (but not the working tree) to the new\n     +-\t\t\t * branch, leaving the working tree as the\n     +-\t\t\t * merged version, but skipping unmerged\n     +-\t\t\t * entries in the index.\n     +-\t\t\t */\n     +-\n     +-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n     +-\t\t\t\t\t0, 0);\n     +-\t\t\tinit_ui_merge_options(&o, the_repository);\n     +-\t\t\to.verbosity = 0;\n     +-\t\t\twork = write_in_core_index_as_tree(the_repository);\n     +-\n     +-\t\t\tret = reset_tree(new_tree,\n     +-\t\t\t\t\t opts, 1,\n     +-\t\t\t\t\t writeout_error, new_branch_info);\n     +-\t\t\tif (ret)\n     +-\t\t\t\treturn ret;\n     +-\t\t\to.ancestor = old_branch_info->name;\n     +-\t\t\tif (!old_branch_info->name) {\n     +-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n     +-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n     +-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n     +-\t\t\t\to.ancestor = old_commit_shortname.buf;\n     +-\t\t\t}\n     +-\t\t\to.branch1 = new_branch_info->name;\n     +-\t\t\to.branch2 = \"local\";\n     +-\t\t\to.conflict_style = opts->conflict_style;\n     +-\t\t\tret = merge_ort_nonrecursive(&o,\n     +-\t\t\t\t\t\t     new_tree,\n     +-\t\t\t\t\t\t     work,\n     +-\t\t\t\t\t\t     old_tree);\n     +-\t\t\tif (ret < 0)\n     +-\t\t\t\tdie(NULL);\n     +-\t\t\tret = reset_tree(new_tree,\n     +-\t\t\t\t\t opts, 0,\n     +-\t\t\t\t\t writeout_error, new_branch_info);\n     +-\t\t\tstrbuf_release(&o.obuf);\n     +-\t\t\tstrbuf_release(&old_commit_shortname);\n     +-\t\t\tif (ret)\n     +-\t\t\t\treturn ret;\n     +-\t\t}\n     ++\t\tif (ret == -1)\n     ++\t\t\treturn 1;\n     + \t}\n     + \n     + \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n     +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\n     + \t\tdie(_(\"unable to write new index file\"));\n     + \n     +-\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n     +-\t\tshow_local_changes(&new_branch_info->commit->object, &opts->diff_options);\n     +-\n     + \treturn 0;\n     + }\n     + \n      @@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n       \trelease_revisions(&revs);\n       }\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \t\t\tdo_merge = 0;\n       \t}\n       \n     -+\tif (opts->autostash) {\n     ++\tif (opts->merge) {\n      +\t\tif (repo_read_index(the_repository) < 0)\n      +\t\t\tdie(_(\"index file corrupt\"));\n     -+\t\tif (opts->discard_changes ||\n     -+\t\t    checkout_would_clobber_changes(&old_branch_info,\n     ++\t\tif (checkout_would_clobber_changes(&old_branch_info,\n      +\t\t\t\t\t\t   new_branch_info))\n      +\t\t\tcreate_autostash_ref(the_repository,\n      +\t\t\t\t\t     \"CHECKOUT_AUTOSTASH\");\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \n       \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n       \n     ++\tif (opts->conflict_style >= 0) {\n     ++\t\tstruct strbuf cfg = STRBUF_INIT;\n     ++\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n     ++\t\t\t    conflict_style_name(opts->conflict_style));\n     ++\t\tgit_config_push_parameter(cfg.buf);\n     ++\t\tstrbuf_release(&cfg);\n     ++\t}\n      +\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n     ++\n     ++\tdiscard_index(the_repository->index);\n     ++\tif (repo_read_index(the_repository) < 0)\n     ++\t\tdie(_(\"index file corrupt\"));\n     ++\n     ++\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n     ++\t\tshow_local_changes(&new_branch_info->commit->object,\n     ++\t\t\t\t   &opts->diff_options);\n      +\n       \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n       \tbranch_info_release(&old_branch_info);\n       \n     -@@ builtin/checkout.c: static int git_checkout_config(const char *var, const char *value,\n     - \t\topts->dwim_new_local_branch = git_config_bool(var, value);\n     - \t\treturn 0;\n     - \t}\n     -+\tif (!strcmp(var, \"checkout.autostash\")) {\n     -+\t\topts->autostash = git_config_bool(var, value);\n     -+\t\treturn 0;\n     -+\t}\n     - \n     - \tif (starts_with(var, \"submodule.\"))\n     - \t\treturn git_default_submodule_config(var, value, NULL);\n     -@@ builtin/checkout.c: static struct option *add_common_switch_branch_options(\n     - \t\t\t   PARSE_OPT_NOCOMPLETE),\n     - \t\tOPT_BOOL(0, \"ignore-other-worktrees\", &opts->ignore_other_worktrees,\n     - \t\t\t N_(\"do not check if another worktree is using this branch\")),\n     -+\t\tOPT_AUTOSTASH(&opts->autostash),\n     - \t\tOPT_END()\n     - \t};\n     - \tstruct option *newopts = parse_options_concat(prevopts, options);\n      \n     - ## t/meson.build ##\n     -@@ t/meson.build: integration_tests = [\n     -   't2030-unresolve-info.sh',\n     -   't2050-git-dir-relative.sh',\n     -   't2060-switch.sh',\n     -+  't2061-switch-autostash.sh',\n     -   't2070-restore.sh',\n     -   't2071-restore-patch.sh',\n     -   't2072-restore-pathspec-file.sh',\n     + ## sequencer.c ##\n     +@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     + \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n     + \t\t}\n     + \n     +-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n     ++\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n     + \t\tif (reset_head(r, &ropts) < 0)\n     + \t\t\tdie(_(\"could not reset --hard\"));\n     + \t\tdiscard_index(r->index);\n      \n     - ## t/t2061-switch-autostash.sh (new) ##\n     -@@\n     -+#!/bin/sh\n     -+\n     -+test_description='checkout/switch --autostash tests'\n     -+\n     -+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n     -+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n     -+\n     -+. ./test-lib.sh\n     -+\n     -+test_expect_success 'setup' '\n     -+\techo file0content >file0 &&\n     -+\techo file1content >file1 &&\n     -+\tgit add . &&\n     -+\ttest_tick &&\n     -+\tgit commit -m \"initial commit\" &&\n     -+\tgit branch other-branch &&\n     -+\techo file1main >file1 &&\n     -+\tgit add . &&\n     -+\ttest_tick &&\n     -+\tgit commit -m \"modify file1 on main\" &&\n     -+\tgit checkout other-branch &&\n     -+\techo file1other >file1 &&\n     -+\tgit add . &&\n     -+\ttest_tick &&\n     -+\tgit commit -m \"modify file1 on other-branch\" &&\n     -+\techo file2content >file2 &&\n     -+\tgit add . &&\n     -+\ttest_tick &&\n     -+\tgit commit -m \"add file2 on other-branch\" &&\n     -+\tgit checkout main\n     + ## t/t7201-co.sh ##\n     +@@ t/t7201-co.sh: test_expect_success 'format of merge conflict from checkout -m' '\n     + \ttest_cmp expect current &&\n     + \n     + \tcat <<-EOF >expect &&\n     +-\t<<<<<<< simple\n     ++\t<<<<<<< Updated upstream\n     + \ta\n     + \tc\n     + \te\n     + \t=======\n     + \tb\n     + \td\n     +-\t>>>>>>> local\n     ++\t>>>>>>> Stashed changes\n     + \tEOF\n     + \ttest_cmp expect two\n     + '\n     +@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n     + \tgit checkout --merge --conflict=diff3 simple &&\n     + \n     + \tcat <<-EOF >expect &&\n     +-\t<<<<<<< simple\n     ++\t<<<<<<< Updated upstream\n     + \ta\n     + \tc\n     + \te\n     +-\t||||||| main\n     ++\t||||||| Stash base\n     + \ta\n     + \tb\n     + \tc\n     +@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n     + \t=======\n     + \tb\n     + \td\n     +-\t>>>>>>> local\n     ++\t>>>>>>> Stashed changes\n     + \tEOF\n     + \ttest_cmp expect two\n     + '\n     + \n     ++test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n     ++\tgit checkout -f main &&\n     ++\tgit reset --hard &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill a b X d e >two &&\n     ++\tgit checkout --merge --conflict=zdiff3 simple &&\n     ++\n     ++\tcat <<-EOF >expect &&\n     ++\ta\n     ++\t<<<<<<< Updated upstream\n     ++\tc\n     ++\t||||||| Stash base\n     ++\tb\n     ++\tc\n     ++\td\n     ++\t=======\n     ++\tb\n     ++\tX\n     ++\td\n     ++\t>>>>>>> Stashed changes\n     ++\te\n     ++\tEOF\n     ++\ttest_cmp expect two\n      +'\n      +\n     -+test_expect_success 'switch --autostash skips stash when no conflict' '\n     -+\tgit branch branch1 other-branch &&\n     -+\techo dirty >file0 &&\n     -+\tgit switch --autostash branch1 >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit switch main\n     ++test_expect_success 'checkout -m respects merge.conflictStyle config' '\n     ++\tgit checkout -f main &&\n     ++\tgit reset --hard &&\n     ++\tgit clean -f &&\n     ++\n     ++\ttest_config merge.conflictStyle diff3 &&\n     ++\tfill b d >two &&\n     ++\tgit checkout -m simple &&\n     ++\n     ++\tcat <<-EOF >expect &&\n     ++\t<<<<<<< Updated upstream\n     ++\ta\n     ++\tc\n     ++\te\n     ++\t||||||| Stash base\n     ++\ta\n     ++\tb\n     ++\tc\n     ++\td\n     ++\te\n     ++\t=======\n     ++\tb\n     ++\td\n     ++\t>>>>>>> Stashed changes\n     ++\tEOF\n     ++\ttest_cmp expect two\n      +'\n      +\n     -+test_expect_success 'checkout --autostash skips stash when no conflict' '\n     -+\tgit branch branch2 other-branch &&\n     -+\techo dirty >file0 &&\n     -+\tgit checkout --autostash branch2 >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit checkout main\n     -+'\n     ++test_expect_success 'checkout -m skips stash when no conflict' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n      +\n     -+test_expect_success 'switch: checkout.autostash config skips stash when no conflict' '\n     -+\tgit branch branch3 other-branch &&\n     -+\techo dirty >file0 &&\n     -+\ttest_config checkout.autostash true &&\n     -+\tgit switch branch3 >actual 2>&1 &&\n     ++\tfill 0 x y z >same &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n      +\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit switch main\n     ++\tfill 0 x y z >expect &&\n     ++\ttest_cmp expect same\n      +'\n      +\n     -+test_expect_success 'checkout: checkout.autostash config skips stash when no conflict' '\n     -+\tgit branch branch4 other-branch &&\n     -+\techo dirty >file0 &&\n     -+\ttest_config checkout.autostash true &&\n     -+\tgit checkout branch4 >actual 2>&1 &&\n     ++test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill 0 x y z >same &&\n     ++\tgit add same &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n      +\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit checkout main\n     ++\tfill 0 x y z >expect &&\n     ++\ttest_cmp expect same\n      +'\n      +\n     -+test_expect_success '--no-autostash overrides checkout.autostash' '\n     -+\tgit branch branch5 other-branch &&\n     -+\techo dirty >file1 &&\n     -+\ttest_config checkout.autostash true &&\n     -+\ttest_must_fail git switch --no-autostash branch5 2>stderr &&\n     -+\ttest_grep ! \"Created autostash\" stderr &&\n     -+\tgit checkout -- file1\n     -+'\n     ++test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n      +\n     -+test_expect_success '--autostash overrides checkout.autostash=false' '\n     -+\tgit branch branch6 other-branch &&\n     -+\techo dirty >file0 &&\n     -+\ttest_config checkout.autostash false &&\n     -+\tgit switch --autostash branch6 >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit switch main\n     ++\tfill 1 2 3 4 5 6 7 >one &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n     ++\ttest_grep \"Created autostash\" actual &&\n     ++\ttest_grep \"Applied autostash\" actual &&\n     ++\tfill 1 2 3 4 5 6 7 >expect &&\n     ++\ttest_cmp expect one\n      +'\n      +\n     -+test_expect_success 'autostash with non-conflicting dirty index' '\n     -+\tgit branch branch7 other-branch &&\n     -+\techo dirty-index >file0 &&\n     -+\tgit add file0 &&\n     -+\tgit switch --autostash branch7 >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty-index >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit checkout -- file0 &&\n     -+\tgit switch main\n     ++test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill 0 x y z >same &&\n     ++\tgit add same &&\n     ++\tfill 1 2 3 4 5 6 7 >one &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n     ++\ttest_grep \"Created autostash\" actual &&\n     ++\ttest_grep \"Applied autostash\" actual &&\n     ++\tfill 0 x y z >expect &&\n     ++\ttest_cmp expect same &&\n     ++\tfill 1 2 3 4 5 6 7 >expect &&\n     ++\ttest_cmp expect one\n      +'\n      +\n     -+test_expect_success 'autostash bypasses conflicting local changes' '\n     -+\tgit branch branch8 other-branch &&\n     -+\techo dirty >file1 &&\n     -+\ttest_must_fail git switch branch8 2>stderr &&\n     ++test_expect_success 'checkout -m stashes on truly conflicting changes' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill 1 2 3 4 5 >one &&\n     ++\ttest_must_fail git checkout side 2>stderr &&\n      +\ttest_grep \"Your local changes\" stderr &&\n     -+\tgit switch --autostash branch8 >actual 2>&1 &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n      +\ttest_grep \"Created autostash\" actual &&\n      +\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n      +\ttest_grep \"Your changes are safe in the stash\" actual &&\n      +\tgit stash drop &&\n     -+\tgit reset --hard &&\n     -+\tgit switch main\n     ++\tgit reset --hard\n      +'\n      +\n     -+test_expect_success 'autostash is a no-op with clean worktree' '\n     -+\tgit branch branch9 other-branch &&\n     -+\tgit switch --autostash branch9 >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\tgit switch main\n     -+'\n     -+\n     -+test_expect_success '--autostash with --merge skips stash when no conflict' '\n     -+\tgit branch branch10 other-branch &&\n     -+\techo dirty >file0 &&\n     -+\tgit switch --autostash --merge branch10 >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit switch main\n     -+'\n     ++test_expect_success 'checkout -m produces usable stash on conflict' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n      +\n     -+test_expect_success 'autostash with staged conflicting changes' '\n     -+\tgit branch branch11 other-branch &&\n     -+\techo staged-change >file1 &&\n     -+\tgit add file1 &&\n     -+\tgit switch --autostash branch11 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n     ++\tfill 1 2 3 4 5 >one &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n      +\ttest_grep \"Your changes are safe in the stash\" actual &&\n     -+\tgit stash drop &&\n     -+\tgit reset --hard &&\n     -+\tgit switch main\n     ++\tgit checkout -f main &&\n     ++\tgit stash pop &&\n     ++\tfill 1 2 3 4 5 >expect &&\n     ++\ttest_cmp expect one\n      +'\n      +\n     -+test_expect_success '--autostash with --force preserves dirty changes' '\n     -+\tgit branch branch12 other-branch &&\n     -+\techo dirty-force >file1 &&\n     -+\tgit switch --autostash --force branch12 >actual 2>&1 &&\n     ++test_expect_success 'checkout -m stashes on staged conflicting changes' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill 1 2 3 4 5 >one &&\n     ++\tgit add one &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n      +\ttest_grep \"Created autostash\" actual &&\n      +\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n      +\ttest_grep \"Your changes are safe in the stash\" actual &&\n      +\tgit stash drop &&\n     -+\tgit reset --hard &&\n     -+\tgit switch main\n     ++\tgit reset --hard\n      +'\n      +\n     -+test_expect_success '--autostash with new branch creation skips stash' '\n     -+\techo dirty >file0 &&\n     -+\tgit switch --autostash -c branch13 >actual 2>&1 &&\n     ++test_expect_success 'checkout -m -b skips stash with dirty tree' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill 0 x y z >same &&\n     ++\tgit checkout -m -b newbranch >actual 2>&1 &&\n      +\ttest_grep ! \"Created autostash\" actual &&\n     -+\techo dirty >expected &&\n     -+\ttest_cmp expected file0 &&\n     -+\tgit switch main &&\n     -+\tgit branch -D branch13\n     ++\tfill 0 x y z >expect &&\n     ++\ttest_cmp expect same &&\n     ++\tgit checkout main &&\n     ++\tgit branch -D newbranch\n      +'\n      +\n     -+test_expect_success 'autostash with conflicting changes that apply cleanly' '\n     -+\tgit branch branch14 other-branch &&\n     -+\techo file1other >file1 &&\n     -+\tgit switch --autostash branch14 >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     -+\techo file1other >expected &&\n     -+\ttest_cmp expected file1 &&\n     -+\tgit switch main\n     -+'\n     + test_expect_success 'switch to another branch while carrying a deletion' '\n     + \tgit checkout -f main &&\n     + \tgit reset --hard &&\n     +\n     + ## xdiff-interface.c ##\n     +@@ xdiff-interface.c: int parse_conflict_style_name(const char *value)\n     + \t\treturn -1;\n     + }\n     + \n     ++const char *conflict_style_name(int style)\n     ++{\n     ++\tswitch (style) {\n     ++\tcase XDL_MERGE_DIFF3:\n     ++\t\treturn \"diff3\";\n     ++\tcase XDL_MERGE_ZEALOUS_DIFF3:\n     ++\t\treturn \"zdiff3\";\n     ++\tdefault:\n     ++\t\treturn \"merge\";\n     ++\t}\n     ++}\n      +\n     -+test_done\n     + int git_xmerge_style = -1;\n     + \n     + int git_xmerge_config(const char *var, const char *value,\n      \n     - ## t/t9902-completion.sh ##\n     -@@ t/t9902-completion.sh: test_expect_success 'double dash \"git checkout\"' '\n     - \t--ignore-other-worktrees Z\n     - \t--recurse-submodules Z\n     - \t--auto-advance Z\n     -+\t--autostash Z\n     - \t--progress Z\n     - \t--guess Z\n     - \t--no-guess Z\n     + ## xdiff-interface.h ##\n     +@@ xdiff-interface.h: void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n     + void xdiff_clear_find_func(xdemitconf_t *xecfg);\n     + struct config_context;\n     + int parse_conflict_style_name(const char *value);\n     ++const char *conflict_style_name(int style);\n     + int git_xmerge_config(const char *var, const char *value,\n     + \t\t      const struct config_context *ctx, void *cb);\n     + extern int git_xmerge_style;\n\n\n Documentation/git-checkout.adoc |  50 +++++-----\n Documentation/git-switch.adoc   |  26 ++---\n builtin/checkout.c              | 160 +++++++++++++++---------------\n sequencer.c                     |   2 +-\n t/t7201-co.sh                   | 170 +++++++++++++++++++++++++++++++-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 7 files changed, 293 insertions(+), 128 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..1e8adf6ef3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,17 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list so you can use `git stash\n+\tpop` to recover and `git stash drop` when done.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +575,36 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would save the local\n+changes in a stash entry and reset the working tree to allow switching:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Created autostash: 7a9afa3\n+Applied autostash.\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the locally modified files overlap with files that need to be\n+updated by the branch switch, the changes are stashed and reapplied\n+after the switch.  If the stash application results in conflicts,\n+they are not resolved and the stash is saved to the stash list:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n+Created autostash: 7a9afa3\n+Applying autostash resulted in conflicts.\n+Your changes are safe in the stash.\n+You can run \"git stash pop\" or \"git stash drop\" at any time.\n ------------\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n-\n-------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+At this point, `git stash pop` can be used to recover and resolve\n+the conflicts, and `git stash drop` to discard the stash when done.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..350e760fd2 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -126,15 +126,14 @@ variable.\n \tIf you have local modifications to one or more files that are\n \tdifferent between the current branch and the branch to which\n \tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\torder to preserve your modifications in context.  With this\n+\toption, the conflicting local changes are automatically\n+\tstashed before the switch and reapplied afterwards.  If the\n+\tlocal changes do not overlap with the differences between\n+\tbranches, the switch proceeds without stashing.  If\n+\treapplying the stash results in conflicts, the entry is\n+\tsaved to the stash list so you can use `git stash pop` to\n+\trecover and `git stash drop` when done.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +216,16 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would save the local\n+changes in a stash entry and reset the working tree to allow switching:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Created autostash: 7a9afa3\n+Applied autostash.\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1d1667fa4c..d3b3b815a7 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -845,83 +845,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n-\t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t}\n+\t\tif (ret == -1)\n+\t\t\treturn 1;\n \t}\n \n \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n@@ -930,9 +855,6 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n-\t\tshow_local_changes(&new_branch_info->commit->object, &opts->diff_options);\n-\n \treturn 0;\n }\n \n@@ -1157,6 +1079,55 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n \trelease_revisions(&revs);\n }\n \n+static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n+\t\t\t\t\t  struct branch_info *new_branch_info)\n+{\n+\tstruct tree_desc trees[2];\n+\tstruct tree *old_tree, *new_tree;\n+\tstruct unpack_trees_options topts;\n+\tstruct index_state tmp_index = INDEX_STATE_INIT(the_repository);\n+\tconst struct object_id *old_commit_oid;\n+\tint ret;\n+\n+\tif (!new_branch_info->commit)\n+\t\treturn 0;\n+\n+\told_commit_oid = old_branch_info->commit ?\n+\t\t&old_branch_info->commit->object.oid :\n+\t\tthe_hash_algo->empty_tree;\n+\told_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);\n+\tif (!old_tree)\n+\t\treturn 0;\n+\n+\tnew_tree = repo_get_commit_tree(the_repository,\n+\t\t\t\t\tnew_branch_info->commit);\n+\tif (!new_tree)\n+\t\treturn 0;\n+\tif (repo_parse_tree(the_repository, new_tree) < 0)\n+\t\treturn 0;\n+\n+\tmemset(&topts, 0, sizeof(topts));\n+\ttopts.head_idx = -1;\n+\ttopts.src_index = the_repository->index;\n+\ttopts.dst_index = &tmp_index;\n+\ttopts.initial_checkout = is_index_unborn(the_repository->index);\n+\ttopts.merge = 1;\n+\ttopts.update = 1;\n+\ttopts.dry_run = 1;\n+\ttopts.quiet = 1;\n+\ttopts.fn = twoway_merge;\n+\n+\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n+\t\t       old_tree->buffer, old_tree->size);\n+\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n+\t\t       new_tree->buffer, new_tree->size);\n+\n+\tret = unpack_trees(2, trees, &topts);\n+\tdiscard_index(&tmp_index);\n+\n+\treturn ret != 0;\n+}\n+\n static int switch_branches(const struct checkout_opts *opts,\n \t\t\t   struct branch_info *new_branch_info)\n {\n@@ -1202,9 +1173,19 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (opts->merge) {\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\t\tif (checkout_would_clobber_changes(&old_branch_info,\n+\t\t\t\t\t\t   new_branch_info))\n+\t\t\tcreate_autostash_ref(the_repository,\n+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH\");\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n \t\t\tbranch_info_release(&old_branch_info);\n \t\t\treturn ret;\n \t\t}\n@@ -1215,6 +1196,23 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex aafd0bc959..72f0afe609 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4677,7 +4677,7 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..9ace3962ba 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -171,14 +171,14 @@ test_expect_success 'format of merge conflict from checkout -m' '\n \ttest_cmp expect current &&\n \n \tcat <<-EOF >expect &&\n-\t<<<<<<< simple\n+\t<<<<<<< Updated upstream\n \ta\n \tc\n \te\n \t=======\n \tb\n \td\n-\t>>>>>>> local\n+\t>>>>>>> Stashed changes\n \tEOF\n \ttest_cmp expect two\n '\n@@ -192,11 +192,11 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \tgit checkout --merge --conflict=diff3 simple &&\n \n \tcat <<-EOF >expect &&\n-\t<<<<<<< simple\n+\t<<<<<<< Updated upstream\n \ta\n \tc\n \te\n-\t||||||| main\n+\t||||||| Stash base\n \ta\n \tb\n \tc\n@@ -205,11 +205,171 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \t=======\n \tb\n \td\n-\t>>>>>>> local\n+\t>>>>>>> Stashed changes\n \tEOF\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< Updated upstream\n+\tc\n+\t||||||| Stash base\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> Stashed changes\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< Updated upstream\n+\ta\n+\tc\n+\te\n+\t||||||| Stash base\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> Stashed changes\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Created autostash\" actual &&\n+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n+\ttest_grep \"Your changes are safe in the stash\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n\nbase-commit: 67006b9db8b772423ad0706029286096307d2567\n-- \ngitgitgadget\n"},{"id":"538899","messageId":"953b5842-a4ae-40f6-8cae-c4f81239c903@gmail.com","threadId":"65220","inReplyTo":"xmqqeclpi00y.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-13T14:29:45Z","receivedAt":"2026-03-13T14:29:49Z","isPatch":true,"body":"On 12/03/2026 14:40, Junio C Hamano wrote:\n> \n> Perhaps doing it this way would make it more worth doing?\n> \n>   - At the beginning of branch switching, ask a new helper function\n>     that takes the branch we are switching to as an argument this\n>     question:\n> \n>     Do any paths that are different between the current branch and\n>     the branch we are switching to have local (i.e., either in the\n>     index or in the working tree) change [Yes/No]?\n> \n>   - When the answer is \"yes\", save the local changes to a new stash\n>     entry, and clear the local changes from the index and from the\n>     working tree.  If not, do not bother with stash at all.\n\nCan we avoid the extra check and stash if the user passed \"--autostash\" \nand unpack_trees() fails because it would overwrite local changes in \nmerge_working_tree()?\n\n> If we can sell it as an improved implementation of \"-m\", we probably\n> can lose some code that the current \"-m\" implementation uses to do\n> its merge; we'd be instead using the \"unstash\" code paths.\n\nThat would be nice but I think \"git checkout --recurse-submodules -m \n<branch>\" currently updates submodules whereas \"git stash\" does not know \nhow to recurse submodules.\n\nIt would be nice to teach \"git stash\" to recurse submodules but I don't \nthink it is completly straight forward as we'd need to store the object \nid of the submodule's stash commit in the parent stash.\n\nThanks\n\nPhillip\n\n"},{"id":"538915","messageId":"xmqqv7ez6455.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v3.git.git.1773393818235.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-13T17:16:54Z","receivedAt":"2026-03-13T17:16:56Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When switching branches, local modifications in the working tree can\n> prevent the checkout from succeeding.  While \"git rebase\" and \"git\n> merge\" already support --autostash to handle this case automatically,\n> \"git checkout\" and \"git switch\" require users to manually stash and\n> unstash their changes.\n>\n> Teach \"git checkout\" and \"git switch\" to accept --autostash and\n> --no-autostash options that automatically create a temporary stash\n> entry before the branch switch begins and apply it after the switch\n> completes.  If the stash application results in conflicts, the stash\n> entry is saved to the stash list so the user can resolve them later.\n>\n> Also add a checkout.autoStash configuration option that enables this\n> behavior by default, which can be overridden with --no-autostash on\n> the command line.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>     checkout: 'autostash' for branch switching\n\nAlmost all of the above is now stale, as we no longer call this the\n\"autostash\" feature.  It turned into a project to vastly improve the\n\"checkout --merge\" option, so the proposed log message needs to be\nrevamped to match.\n\nUsing the autostash mechanism to simplify checkout.c is a great\ndirection, I think, provided we can maintain the high-quality\nconflict information (branch labels) that users expect from '-m'.\n\nFor a new iteration that invalidates almost everything from the\nprevious round, it is easier if range-diff were omitted from the\nreviewers' point of view (I am not a GGG user, so I do not know if\nthere is such an option to do so).  It is not a huge deal as we can\nsimply ignore the large block of lines as irrelevant ;-)\n\n>  Documentation/git-checkout.adoc |  50 +++++-----\n>  Documentation/git-switch.adoc   |  26 ++---\n>  builtin/checkout.c              | 160 +++++++++++++++---------------\n>  sequencer.c                     |   2 +-\n>  t/t7201-co.sh                   | 170 +++++++++++++++++++++++++++++++-\n>  xdiff-interface.c               |  12 +++\n>  xdiff-interface.h               |   1 +\n>  7 files changed, 293 insertions(+), 128 deletions(-)\n>\n> diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\n> index 43ccf47cf6..1e8adf6ef3 100644\n> --- a/Documentation/git-checkout.adoc\n> +++ b/Documentation/git-checkout.adoc\n> @@ -251,20 +251,17 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n>  \tare different between the current branch and the branch to\n>  \twhich you are switching, the command refuses to switch\n>  \tbranches in order to preserve your modifications in context.\n> +\tWith this option, the conflicting local changes are\n> +\tautomatically stashed before the switch and reapplied\n> +\tafterwards.  If the local changes do not overlap with the\n> +\tdifferences between branches, the switch proceeds without\n> +\tstashing.  If reapplying the stash results in conflicts, the\n> +\tentry is saved to the stash list so you can use `git stash\n> +\tpop` to recover and `git stash drop` when done.\n\nGreat.\n\n> -+\n> -When switching branches with `--merge`, staged changes may be lost.\n\nHmph, I have to admit that I never do \"checkout\" to go to a different\nbranch with a dirty index that does not match the current HEAD, with\nor without \"--merge\"; in fact, not just \"I never do\" myself, I never\nthought about what happens when somebody does so, so I do not offhand\nknow what the current code without this patch does, or what the updated\ncode would.  In \"stash && switch && unstash\" flow, the last \"unstash\"\nis what decides if the original changes in the index is reapplied to\nthe index or not, IIRC, so we still may lose the distinction between\nwhat has been and what has not been added to the index after all,\ndepending on the way how the new code does the \"unstash\" step.\n\n> @@ -578,39 +575,36 @@ $ git checkout mytopic\n>  error: You have local changes to 'frotz'; not switching branches.\n>  ------------\n>  \n> -You can give the `-m` flag to the command, which would try a\n> -three-way merge:\n> +You can give the `-m` flag to the command, which would save the local\n> +changes in a stash entry and reset the working tree to allow switching:\n>  \n>  ------------\n>  $ git checkout -m mytopic\n> -Auto-merging frotz\n> +Created autostash: 7a9afa3\n> +Applied autostash.\n>  ------------\n>  \n> -After this three-way merge, the local modifications are _not_\n> +After the switch, the local modifications are reapplied and are _not_\n>  registered in your index file, so `git diff` would show you what\n>  changes you made since the tip of the new branch.\n\nOK.\n\n>  === 3. Merge conflict\n>  \n> -When a merge conflict happens during switching branches with\n> -the `-m` option, you would see something like this:\n> +When the locally modified files overlap with files that need to be\n> +updated by the branch switch, the changes are stashed and reapplied\n> +after the switch.  If the stash application results in conflicts,\n\nIn the original text of this third example (and as I understand,\nthese examples are written to be read more-or-less independently),\nthe `-m` option was explicitly mentioned to make it clear that this\n\"conflict\" situation is relevant only when the option is used.  In\nthe updated text, it is not.  We should mention `-m` somewhere in\nthe updated text as well: When the `--merge` (`-m`) option is in\neffect, _this_ happens.\n\n> +they are not resolved and the stash is saved to the stash list:\n>  \n>  ------------\n>  $ git checkout -m mytopic\n> -Auto-merging frotz\n> -ERROR: Merge conflict in frotz\n> -fatal: merge program failed\n> +Created autostash: 7a9afa3\n> +Applying autostash resulted in conflicts.\n> +Your changes are safe in the stash.\n> +You can run \"git stash pop\" or \"git stash drop\" at any time.\n>  ------------\n\nNice.  It may probably be on a bit too verbose side, though.  The\nfact we are creating a stash entry or the object name of it is not\ninteresting to the user unless there is a conflict, so I would have\nthought we would not say \"Created autostash\" at all, and the last\nthree lines would be replaced with something like the following\nand shown _only_ when the application of the stashed changes did not\nsucceed cleanly:\n\n    Your local changes are stashed, however, applying it to carry\n    forward your local changes resulted in conflicts:\n\n     - You can try resolving them now.  If you resolved them\n       successfully, discard the stash entry with \"git stash drop\".\n\n     - Alternatively you can \"git reset --hard\" if you do not want\n       to deal with them right now, and later \"git stash pop\" to\n       recover your local changes.\n\n> -At this point, `git diff` shows the changes cleanly merged as in\n> -the previous example, as well as the changes in the conflicted\n> -files.  Edit and resolve the conflict and mark it resolved with\n> -`git add` as usual:\n> -\n> -------------\n> -$ edit frotz\n> -$ git add frotz\n> -------------\n\nAfter resolving and adding, they need to do \"git stash drop\" it, but\nother than that, this part of the example is still valid, isn't it?\n\nThe improvement this patch brings in is that the user now has\nanother choice, i.e.,\n\n    Instead of editing and resolving the conflicts right now, you\n    can clear the slate with `git reset --hard` and resurrect the\n    local changes, safely stored in the stash at your leisure, with\n    `git stash pop`.\n\n> +At this point, `git stash pop` can be used to recover and resolve\n> +the conflicts, and `git stash drop` to discard the stash when done.\n\nThis is not giving a wrong instruction per-se, but my suggestion\nabove can be used to extend it if we wanted to.  It may make it\neasier to understand to the readers, or it may make it way too\nverbose.  I dunno.\n\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 1d1667fa4c..d3b3b815a7 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -17,7 +17,6 @@\n>  #include \"merge-ll.h\"\n>  #include \"lockfile.h\"\n>  #include \"mem-pool.h\"\n> -#include \"merge-ort-wrappers.h\"\n>  #include \"object-file.h\"\n>  #include \"object-name.h\"\n>  #include \"odb.h\"\n> @@ -30,6 +29,7 @@\n>  #include \"repo-settings.h\"\n>  #include \"resolve-undo.h\"\n>  #include \"revision.h\"\n> +#include \"sequencer.h\"\n>  #include \"setup.h\"\n>  #include \"submodule.h\"\n>  #include \"symlinks.h\"\n> @@ -845,83 +845,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>  \n>  \t\tret = unpack_trees(2, trees, &topts);\n>  \t\tclear_unpack_trees_porcelain(&topts);\n> -\t\tif (ret == -1) {\n> -\t\t\t/*\n> -\t\t\t * Unpack couldn't do a trivial merge; either\n> -\t\t\t * give up or do a real merge, depending on\n> -\t\t\t * whether the merge flag was used.\n> -\t\t\t */\n> -...\n> -\t\t}\n> +\t\tif (ret == -1)\n> +\t\t\treturn 1;\n>  \t}\n\nSo pleased to see so much custom code getting removed ;-).\n\n> diff --git a/sequencer.c b/sequencer.c\n> index aafd0bc959..72f0afe609 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4677,7 +4677,7 @@ static void create_autostash_internal(struct repository *r,\n>  \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n>  \t\t}\n>  \n> -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n> +\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n>  \t\tif (reset_head(r, &ropts) < 0)\n>  \t\t\tdie(_(\"could not reset --hard\"));\n>  \t\tdiscard_index(r->index);\n\nIs this change something all the callers that passes control through\nthis part desire?  Don't some of them want the output go to the\nstandard output, not standard error?\n\nIf we are going in the direction I suggested above (i.e., be silent\nabout our use of stashes before it becomes relevant to the user),\nthis part may have to become a conditional\n\n\tif (!silent)\n\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n\nor something, where all the current callers before the \"checkout\n--merge\" adds a new caller would pass silent==0 to.\n\nWhatever changes this function needs, it looks more like a separate\npreparatory patch to me.\n\n> diff --git a/t/t7201-co.sh b/t/t7201-co.sh\n> index 9bcf7c0b40..9ace3962ba 100755\n> --- a/t/t7201-co.sh\n> +++ b/t/t7201-co.sh\n> @@ -171,14 +171,14 @@ test_expect_success 'format of merge conflict from checkout -m' '\n>  \ttest_cmp expect current &&\n>  \n>  \tcat <<-EOF >expect &&\n> -\t<<<<<<< simple\n> +\t<<<<<<< Updated upstream\n>  \ta\n>  \tc\n>  \te\n>  \t=======\n>  \tb\n>  \td\n> -\t>>>>>>> local\n> +\t>>>>>>> Stashed changes\n>  \tEOF\n>  \ttest_cmp expect two\n>  '\n\nHistorically, 'git checkout -m' provided the branch names (e.g.,\n\"<<<<<<< simple\"), which is very helpful context.  It would be great\nif we could find a way to pass the branch names through as labels to\nthe stash application.  It may require a preparatory change to \"git\nstash apply\" to allow customizing the labels---the underlying\nmachinery at the lowest level does have ability to use labels the\ncallers want it to use, so it may be just the matter of plumbing it\nthrough the callchain.  I didn't check.\n\n> diff --git a/xdiff-interface.c b/xdiff-interface.c\n> index f043330f2a..5ee2b96d0a 100644\n> --- a/xdiff-interface.c\n> +++ b/xdiff-interface.c\n> @@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n>  \t\treturn -1;\n>  }\n>  \n> +const char *conflict_style_name(int style)\n> +{\n> +\tswitch (style) {\n> +\tcase XDL_MERGE_DIFF3:\n> +\t\treturn \"diff3\";\n> +\tcase XDL_MERGE_ZEALOUS_DIFF3:\n> +\t\treturn \"zdiff3\";\n> +\tdefault:\n> +\t\treturn \"merge\";\n> +\t}\n> +}\n> +\n>  int git_xmerge_style = -1;\n>  \n>  int git_xmerge_config(const char *var, const char *value,\n> diff --git a/xdiff-interface.h b/xdiff-interface.h\n> index fbc4ceec40..ce54e1c0e0 100644\n> --- a/xdiff-interface.h\n> +++ b/xdiff-interface.h\n> @@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n>  void xdiff_clear_find_func(xdemitconf_t *xecfg);\n>  struct config_context;\n>  int parse_conflict_style_name(const char *value);\n> +const char *conflict_style_name(int style);\n>  int git_xmerge_config(const char *var, const char *value,\n>  \t\t      const struct config_context *ctx, void *cb);\n>  extern int git_xmerge_style;\n\nThis may be a useful addition, but it would probably need to become\na separate preparatory patch, possibly with its own test.\n\n"},{"id":"538926","messageId":"20260313193320.42832-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqv7ez6455.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-03-13T19:33:20Z","receivedAt":"2026-03-13T19:33:23Z","isPatch":true,"body":"> Almost all of the above is now stale, as we no longer call this the\n> \"autostash\" feature.  It turned into a project to vastly improve the\n> \"checkout --merge\" option, so the proposed log message needs to be\n> revamped to match.\n\nMy feeling is that this feature will drift far away from what I initially\nneeded. I have never used 'checkout -m' but stash->checkout-unstash is a\npattern I use a lot.\n\nI wonder with how complicated this might turn out, if it would be better to\nhave this as a separate feature called autostash (mirroring the same\nfeature from git pull rebase which I have enabled).\n\nHarald\n"},{"id":"538933","messageId":"xmqq4imj4gln.fsf@gitster.g","threadId":"65220","inReplyTo":"20260313193320.42832-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-13T20:30:44Z","receivedAt":"2026-03-13T20:30:47Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> Almost all of the above is now stale, as we no longer call this the\n>> \"autostash\" feature.  It turned into a project to vastly improve the\n>> \"checkout --merge\" option, so the proposed log message needs to be\n>> revamped to match.\n>\n> My feeling is that this feature will drift far away from what I initially\n> needed. I have never used 'checkout -m' but stash->checkout-unstash is a\n> pattern I use a lot.\n\nPerhaps.  But things like \"checkout --autostash\", which can entirely\nbe done by the end user via a wrapper script or an alias, is not\ninteresting enough from my point of view.\n"},{"id":"538967","messageId":"pull.2234.v4.git.git.1773482375668.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v3.git.git.1773393818235.gitgitgadget@gmail.com","subject":"[PATCH v4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-14T09:59:35Z","receivedAt":"2026-03-14T09:59:40Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    checkout: 'autostash' for branch switching\n    \n    cc: Phillip Wood phillip.wood123@gmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v4\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v3:\n\n 1:  05f1e53163 ! 1:  5d49c0031a checkout: add --autostash option for branch switching\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    checkout: add --autostash option for branch switching\n     +    checkout: -m (--merge) uses autostash when switching branches\n      \n     -    When switching branches, local modifications in the working tree can\n     -    prevent the checkout from succeeding.  While \"git rebase\" and \"git\n     -    merge\" already support --autostash to handle this case automatically,\n     -    \"git checkout\" and \"git switch\" require users to manually stash and\n     -    unstash their changes.\n     -\n     -    Teach \"git checkout\" and \"git switch\" to accept --autostash and\n     -    --no-autostash options that automatically create a temporary stash\n     -    entry before the branch switch begins and apply it after the switch\n     -    completes.  If the stash application results in conflicts, the stash\n     -    entry is saved to the stash list so the user can resolve them later.\n     -\n     -    Also add a checkout.autoStash configuration option that enables this\n     -    behavior by default, which can be overridden with --no-autostash on\n     -    the command line.\n     +    When switching branches with \"git checkout -m\", local modifications\n     +    can block the switch.  Teach the -m flow to create a temporary stash\n     +    before switching and reapply it after.  On success, only \"Applied\n     +    autostash.\" is shown.  If reapplying causes conflicts, the stash is\n     +    kept and the user is told they can resolve and run \"git stash drop\",\n     +    or run \"git reset --hard\" and later \"git stash pop\" to recover their\n     +    changes.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n       ------------\n       $ git checkout -m mytopic\n      -Auto-merging frotz\n     -+Created autostash: 7a9afa3\n      +Applied autostash.\n       ------------\n       \n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n       \n      -When a merge conflict happens during switching branches with\n      -the `-m` option, you would see something like this:\n     -+When the locally modified files overlap with files that need to be\n     -+updated by the branch switch, the changes are stashed and reapplied\n     -+after the switch.  If the stash application results in conflicts,\n     -+they are not resolved and the stash is saved to the stash list:\n     ++When the `--merge` (`-m`) option is in effect and the locally\n     ++modified files overlap with files that need to be updated by the\n     ++branch switch, the changes are stashed and reapplied after the\n     ++switch.  If the stash application results in conflicts, they are not\n     ++resolved and the stash is saved to the stash list:\n       \n       ------------\n       $ git checkout -m mytopic\n      -Auto-merging frotz\n      -ERROR: Merge conflict in frotz\n      -fatal: merge program failed\n     -+Created autostash: 7a9afa3\n     -+Applying autostash resulted in conflicts.\n     -+Your changes are safe in the stash.\n     -+You can run \"git stash pop\" or \"git stash drop\" at any time.\n     - ------------\n     +-------------\n     ++Your local changes are stashed, however, applying it to carry\n     ++forward your local changes resulted in conflicts:\n       \n      -At this point, `git diff` shows the changes cleanly merged as in\n      -the previous example, as well as the changes in the conflicted\n      -files.  Edit and resolve the conflict and mark it resolved with\n      -`git add` as usual:\n     --\n     --------------\n     ++ - You can try resolving them now.  If you resolved them\n     ++   successfully, discard the stash entry with \"git stash drop\".\n     + \n     ++ - Alternatively you can \"git reset --hard\" if you do not want\n     ++   to deal with them right now, and later \"git stash pop\" to\n     ++   recover your local changes.\n     + ------------\n      -$ edit frotz\n      -$ git add frotz\n      -------------\n     -+At this point, `git stash pop` can be used to recover and resolve\n     -+the conflicts, and `git stash drop` to discard the stash when done.\n     ++\n     ++You can try resolving the conflicts now.  Edit the conflicting files\n     ++and mark them resolved with `git add` as usual, then run `git stash\n     ++drop` to discard the stash entry.  Alternatively, you can clear the\n     ++working tree with `git reset --hard` and recover your local changes\n     ++later with `git stash pop`.\n       \n       CONFIGURATION\n       -------------\n     @@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commi\n       static int switch_branches(const struct checkout_opts *opts,\n       \t\t\t   struct branch_info *new_branch_info)\n       {\n     +@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n     + \tstruct object_id rev;\n     + \tint flag, writeout_error = 0;\n     + \tint do_merge = 1;\n     ++\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n     ++\tconst char *stash_label_ancestor = NULL;\n     + \n     + \ttrace2_cmd_mode(\"branch\");\n     + \n      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \t\t\tdo_merge = 0;\n       \t}\n       \n     ++\tif (old_branch_info.name)\n     ++\t\tstash_label_ancestor = old_branch_info.name;\n     ++\telse if (old_branch_info.commit) {\n     ++\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n     ++\t\t\t\t\t &old_branch_info.commit->object.oid,\n     ++\t\t\t\t\t DEFAULT_ABBREV);\n     ++\t\tstash_label_ancestor = old_commit_shortname.buf;\n     ++\t}\n     ++\n      +\tif (opts->merge) {\n      +\t\tif (repo_read_index(the_repository) < 0)\n      +\t\t\tdie(_(\"index file corrupt\"));\n      +\t\tif (checkout_would_clobber_changes(&old_branch_info,\n      +\t\t\t\t\t\t   new_branch_info))\n     -+\t\t\tcreate_autostash_ref(the_repository,\n     -+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH\");\n     ++\t\t\tcreate_autostash_ref_silent(the_repository,\n     ++\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n      +\t}\n      +\n       \tif (do_merge) {\n       \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n       \t\tif (ret) {\n     -+\t\t\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n     ++\t\t\tapply_autostash_ref_with_labels(the_repository,\n     ++\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n     ++\t\t\t\t\t\t       new_branch_info->name,\n     ++\t\t\t\t\t\t       \"local\",\n     ++\t\t\t\t\t\t       stash_label_ancestor);\n       \t\t\tbranch_info_release(&old_branch_info);\n     ++\t\t\tstrbuf_release(&old_commit_shortname);\n       \t\t\treturn ret;\n       \t\t}\n     + \t}\n      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \n       \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\tgit_config_push_parameter(cfg.buf);\n      +\t\tstrbuf_release(&cfg);\n      +\t}\n     -+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH\");\n     ++\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n     ++\t\t\t\t       new_branch_info->name, \"local\",\n     ++\t\t\t\t       stash_label_ancestor);\n      +\n      +\tdiscard_index(the_repository->index);\n      +\tif (repo_read_index(the_repository) < 0)\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\n       \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n       \tbranch_info_release(&old_branch_info);\n     ++\tstrbuf_release(&old_commit_shortname);\n     + \n     + \treturn ret || writeout_error;\n     + }\n     +\n     + ## builtin/stash.c ##\n     +@@ builtin/stash.c: static void unstage_changes_unless_new(struct object_id *orig_tree)\n     + \t\tdie(_(\"could not write index\"));\n     + }\n     + \n     +-static int do_apply_stash(const char *prefix, struct stash_info *info,\n     +-\t\t\t  int index, int quiet)\n     ++static int do_apply_stash_with_labels(const char *prefix,\n     ++\t\t\t\t      struct stash_info *info,\n     ++\t\t\t\t      int index, int quiet,\n     ++\t\t\t\t      const char *label1, const char *label2,\n     ++\t\t\t\t      const char *label_ancestor)\n     + {\n     + \tint clean, ret;\n     + \tint has_index = index;\n     +@@ builtin/stash.c: static int do_apply_stash(const char *prefix, struct stash_info *info,\n       \n     + \tinit_ui_merge_options(&o, the_repository);\n     + \n     +-\to.branch1 = \"Updated upstream\";\n     +-\to.branch2 = \"Stashed changes\";\n     +-\to.ancestor = \"Stash base\";\n     ++\to.branch1 = label1 ? label1 : \"Updated upstream\";\n     ++\to.branch2 = label2 ? label2 : \"Stashed changes\";\n     ++\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n     + \n     + \tif (oideq(&info->b_tree, &c_tree))\n     + \t\to.branch1 = \"Version stash was based on\";\n     +@@ builtin/stash.c: restore_untracked:\n     + \treturn ret;\n     + }\n     + \n     ++static int do_apply_stash(const char *prefix, struct stash_info *info,\n     ++\t\t\t  int index, int quiet)\n     ++{\n     ++\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n     ++\t\t\t\t\t  NULL, NULL, NULL);\n     ++}\n     ++\n     + static int apply_stash(int argc, const char **argv, const char *prefix,\n     + \t\t       struct repository *repo UNUSED)\n     + {\n     + \tint ret = -1;\n     + \tint quiet = 0;\n     + \tint index = use_index;\n     ++\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n     + \tstruct stash_info info = STASH_INFO_INIT;\n     + \tstruct option options[] = {\n     + \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n     + \t\tOPT_BOOL(0, \"index\", &index,\n     + \t\t\t N_(\"attempt to recreate the index\")),\n     ++\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n     ++\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n     ++\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n     ++\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n     ++\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n     ++\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n     + \t\tOPT_END()\n     + \t};\n     + \n     +@@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char *prefix,\n     + \tif (get_stash_info(&info, argc, argv))\n     + \t\tgoto cleanup;\n     + \n     +-\tret = do_apply_stash(prefix, &info, index, quiet);\n     ++\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n     ++\t\t\t\t\t label1, label2, label_ancestor);\n     + cleanup:\n     + \tfree_stash_info(&info);\n     + \treturn ret;\n      \n       ## sequencer.c ##\n     +@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n     + \n     + static void create_autostash_internal(struct repository *r,\n     + \t\t\t\t      const char *path,\n     +-\t\t\t\t      const char *refname)\n     ++\t\t\t\t      const char *refname,\n     ++\t\t\t\t      int silent)\n     + {\n     + \tstruct strbuf buf = STRBUF_INIT;\n     + \tstruct lock_file lock_file = LOCK_INIT;\n      @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n       \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n       \t\t}\n       \n      -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n     -+\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n     ++\t\tif (!silent)\n     ++\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n       \t\tif (reset_head(r, &ropts) < 0)\n       \t\t\tdie(_(\"could not reset --hard\"));\n       \t\tdiscard_index(r->index);\n     +@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     + \n     + void create_autostash(struct repository *r, const char *path)\n     + {\n     +-\tcreate_autostash_internal(r, path, NULL);\n     ++\tcreate_autostash_internal(r, path, NULL, 0);\n     + }\n     + \n     + void create_autostash_ref(struct repository *r, const char *refname)\n     + {\n     +-\tcreate_autostash_internal(r, NULL, refname);\n     ++\tcreate_autostash_internal(r, NULL, refname, 0);\n     + }\n     + \n     +-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     ++void create_autostash_ref_silent(struct repository *r, const char *refname)\n     ++{\n     ++\tcreate_autostash_internal(r, NULL, refname, 1);\n     ++}\n     ++\n     ++static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     ++\t\t\t\t    const char *label1, const char *label2,\n     ++\t\t\t\t    const char *label_ancestor)\n     + {\n     + \tstruct child_process child = CHILD_PROCESS_INIT;\n     + \tint ret = 0;\n     +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     + \t\tchild.no_stderr = 1;\n     + \t\tstrvec_push(&child.args, \"stash\");\n     + \t\tstrvec_push(&child.args, \"apply\");\n     ++\t\tif (label1)\n     ++\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n     ++\t\tif (label2)\n     ++\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n     ++\t\tif (label_ancestor)\n     ++\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n     + \t\tstrvec_push(&child.args, stash_oid);\n     + \t\tret = run_command(&child);\n     + \t}\n     +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     + \t\tstrvec_push(&store.args, stash_oid);\n     + \t\tif (run_command(&store))\n     + \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n     ++\t\telse if (attempt_apply)\n     ++\t\t\tfprintf(stderr,\n     ++\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n     ++\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n     ++\t\t\t\t  \"\\n\"\n     ++\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n     ++\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n     ++\t\t\t\t  \"\\n\"\n     ++\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n     ++\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n     ++\t\t\t\t  \"   recover your local changes.\\n\"));\n     + \t\telse\n     + \t\t\tfprintf(stderr,\n     +-\t\t\t\t_(\"%s\\n\"\n     ++\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n     + \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n     + \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n     +-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n     +-\t\t\t\tattempt_apply ?\n     +-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n     +-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n     ++\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n     + \t}\n     + \n     + \treturn ret;\n     +@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n     + \t}\n     + \tstrbuf_trim(&stash_oid);\n     + \n     +-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n     ++\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n     ++\t\t\t\t      NULL, NULL, NULL);\n     + \n     + \tunlink(path);\n     + \tstrbuf_release(&stash_oid);\n     +@@ sequencer.c: int apply_autostash(const char *path)\n     + \n     + int apply_autostash_oid(const char *stash_oid)\n     + {\n     +-\treturn apply_save_autostash_oid(stash_oid, 1);\n     ++\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n     + }\n     + \n     + static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     +-\t\t\t\t    int attempt_apply)\n     ++\t\t\t\t    int attempt_apply,\n     ++\t\t\t\t    const char *label1, const char *label2,\n     ++\t\t\t\t    const char *label_ancestor)\n     + {\n     + \tstruct object_id stash_oid;\n     + \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n     +@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     + \t\treturn error(_(\"autostash reference is a symref\"));\n     + \n     + \toid_to_hex_r(stash_oid_hex, &stash_oid);\n     +-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n     ++\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n     ++\t\t\t\t       label1, label2, label_ancestor);\n     + \n     + \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n     + \t\t\t&stash_oid, REF_NO_DEREF);\n     +@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     + \n     + int save_autostash_ref(struct repository *r, const char *refname)\n     + {\n     +-\treturn apply_save_autostash_ref(r, refname, 0);\n     ++\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n     + }\n     + \n     + int apply_autostash_ref(struct repository *r, const char *refname)\n     + {\n     +-\treturn apply_save_autostash_ref(r, refname, 1);\n     ++\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n     ++}\n     ++\n     ++int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     ++\t\t\t\t    const char *label1, const char *label2,\n     ++\t\t\t\t    const char *label_ancestor)\n     ++{\n     ++\treturn apply_save_autostash_ref(r, refname, 1,\n     ++\t\t\t\t\tlabel1, label2, label_ancestor);\n     + }\n     + \n     + static int checkout_onto(struct repository *r, struct replay_opts *opts,\n      \n     - ## t/t7201-co.sh ##\n     -@@ t/t7201-co.sh: test_expect_success 'format of merge conflict from checkout -m' '\n     - \ttest_cmp expect current &&\n     - \n     - \tcat <<-EOF >expect &&\n     --\t<<<<<<< simple\n     -+\t<<<<<<< Updated upstream\n     - \ta\n     - \tc\n     - \te\n     - \t=======\n     - \tb\n     - \td\n     --\t>>>>>>> local\n     -+\t>>>>>>> Stashed changes\n     + ## sequencer.h ##\n     +@@ sequencer.h: void commit_post_rewrite(struct repository *r,\n     + \n     + void create_autostash(struct repository *r, const char *path);\n     + void create_autostash_ref(struct repository *r, const char *refname);\n     ++void create_autostash_ref_silent(struct repository *r, const char *refname);\n     + int save_autostash(const char *path);\n     + int save_autostash_ref(struct repository *r, const char *refname);\n     + int apply_autostash(const char *path);\n     + int apply_autostash_oid(const char *stash_oid);\n     + int apply_autostash_ref(struct repository *r, const char *refname);\n     ++int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     ++\t\t\t\t    const char *label1, const char *label2,\n     ++\t\t\t\t    const char *label_ancestor);\n     + \n     + #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n     + #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n     +\n     + ## t/t3420-rebase-autostash.sh ##\n     +@@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n     + \tFirst, rewinding head to replay your work on top of it...\n     + \tApplying: second commit\n     + \tApplying: third commit\n     +-\tApplying autostash resulted in conflicts.\n     +-\tYour changes are safe in the stash.\n     +-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n     ++\tYour local changes are stashed, however, applying it to carry\n     ++\tforward your local changes resulted in conflicts:\n     ++\n     ++\t - You can try resolving them now.  If you resolved them\n     ++\t   successfully, discard the stash entry with \"git stash drop\".\n     ++\n     ++\t - Alternatively you can \"git reset --hard\" if you do not want\n     ++\t   to deal with them right now, and later \"git stash pop\" to\n     ++\t   recover your local changes.\n       \tEOF\n     - \ttest_cmp expect two\n     - '\n     -@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n     - \tgit checkout --merge --conflict=diff3 simple &&\n     - \n     - \tcat <<-EOF >expect &&\n     --\t<<<<<<< simple\n     -+\t<<<<<<< Updated upstream\n     - \ta\n     - \tc\n     - \te\n     --\t||||||| main\n     -+\t||||||| Stash base\n     - \ta\n     - \tb\n     - \tc\n     -@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n     - \t=======\n     - \tb\n     - \td\n     --\t>>>>>>> local\n     -+\t>>>>>>> Stashed changes\n     + }\n     + \n     + create_expected_failure_merge () {\n     + \tcat >expected <<-EOF\n     + \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n     +-\tApplying autostash resulted in conflicts.\n     +-\tYour changes are safe in the stash.\n     +-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n     ++\tYour local changes are stashed, however, applying it to carry\n     ++\tforward your local changes resulted in conflicts:\n     ++\n     ++\t - You can try resolving them now.  If you resolved them\n     ++\t   successfully, discard the stash entry with \"git stash drop\".\n     ++\n     ++\t - Alternatively you can \"git reset --hard\" if you do not want\n     ++\t   to deal with them right now, and later \"git stash pop\" to\n     ++\t   recover your local changes.\n     + \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n       \tEOF\n     + }\n     +\n     + ## t/t7201-co.sh ##\n     +@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n       \ttest_cmp expect two\n       '\n       \n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\n      +\tcat <<-EOF >expect &&\n      +\ta\n     -+\t<<<<<<< Updated upstream\n     ++\t<<<<<<< simple\n      +\tc\n     -+\t||||||| Stash base\n     ++\t||||||| main\n      +\tb\n      +\tc\n      +\td\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tb\n      +\tX\n      +\td\n     -+\t>>>>>>> Stashed changes\n     ++\t>>>>>>> local\n      +\te\n      +\tEOF\n      +\ttest_cmp expect two\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tgit checkout -m simple &&\n      +\n      +\tcat <<-EOF >expect &&\n     -+\t<<<<<<< Updated upstream\n     ++\t<<<<<<< simple\n      +\ta\n      +\tc\n      +\te\n     -+\t||||||| Stash base\n     ++\t||||||| main\n      +\ta\n      +\tb\n      +\tc\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\t=======\n      +\tb\n      +\td\n     -+\t>>>>>>> Stashed changes\n     ++\t>>>>>>> local\n      +\tEOF\n      +\ttest_cmp expect two\n      +'\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\n      +\tfill 1 2 3 4 5 6 7 >one &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\ttest_grep \"Applied autostash\" actual &&\n      +\tfill 1 2 3 4 5 6 7 >expect &&\n      +\ttest_cmp expect one\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tgit add same &&\n      +\tfill 1 2 3 4 5 6 7 >one &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n      +\ttest_grep \"Applied autostash\" actual &&\n      +\tfill 0 x y z >expect &&\n      +\ttest_cmp expect same &&\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\ttest_must_fail git checkout side 2>stderr &&\n      +\ttest_grep \"Your local changes\" stderr &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n     -+\ttest_grep \"Your changes are safe in the stash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n     ++\ttest_grep \"resulted in conflicts\" actual &&\n     ++\ttest_grep \"git stash drop\" actual &&\n      +\tgit stash drop &&\n      +\tgit reset --hard\n      +'\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\n      +\tfill 1 2 3 4 5 >one &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep \"Your changes are safe in the stash\" actual &&\n     ++\ttest_grep \"recover your local changes\" actual &&\n      +\tgit checkout -f main &&\n      +\tgit stash pop &&\n      +\tfill 1 2 3 4 5 >expect &&\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tfill 1 2 3 4 5 >one &&\n      +\tgit add one &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep \"Created autostash\" actual &&\n     -+\ttest_grep \"Applying autostash resulted in conflicts\" actual &&\n     -+\ttest_grep \"Your changes are safe in the stash\" actual &&\n     ++\ttest_grep ! \"Created autostash\" actual &&\n     ++\ttest_grep \"resulted in conflicts\" actual &&\n     ++\ttest_grep \"git stash drop\" actual &&\n      +\tgit stash drop &&\n      +\tgit reset --hard\n      +'\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n       \tgit checkout -f main &&\n       \tgit reset --hard &&\n      \n     + ## t/t7600-merge.sh ##\n     +@@ t/t7600-merge.sh: test_expect_success 'merge --squash --autostash conflict does not attempt to app\n     + \t>unrelated &&\n     + \tgit add unrelated &&\n     + \ttest_must_fail git merge --squash c7 --autostash >out 2>err &&\n     +-\t! grep \"Applying autostash resulted in conflicts.\" err &&\n     ++\t! grep \"resulted in conflicts\" err &&\n     + \tgrep \"When finished, apply stashed changes with \\`git stash pop\\`\" out\n     + '\n     + \n     +@@ t/t7600-merge.sh: test_expect_success 'merge with conflicted --autostash changes' '\n     + \tgit diff >expect &&\n     + \ttest_when_finished \"test_might_fail git stash drop\" &&\n     + \tgit merge --autostash c3 2>err &&\n     +-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n     ++\ttest_grep \"resulted in conflicts\" err &&\n     + \tgit show HEAD:file >merge-result &&\n     + \ttest_cmp result.1-9 merge-result &&\n     + \tgit stash show -p >actual &&\n     +\n       ## xdiff-interface.c ##\n      @@ xdiff-interface.c: int parse_conflict_style_name(const char *value)\n       \t\treturn -1;\n\n\n Documentation/git-checkout.adoc |  56 +++++-----\n Documentation/git-switch.adoc   |  26 ++---\n builtin/checkout.c              | 179 +++++++++++++++++---------------\n builtin/stash.c                 |  30 ++++--\n sequencer.c                     |  67 +++++++++---\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++--\n t/t7201-co.sh                   | 160 ++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   4 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 11 files changed, 412 insertions(+), 151 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..9d5f5c51ae 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,17 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list so you can use `git stash\n+\tpop` to recover and `git stash drop` when done.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +575,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would save the local\n+changes in a stash entry and reset the working tree to allow switching:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Applied autostash.\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If the stash application results in conflicts, they are not\n+resolved and the stash is saved to the stash list:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..350e760fd2 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -126,15 +126,14 @@ variable.\n \tIf you have local modifications to one or more files that are\n \tdifferent between the current branch and the branch to which\n \tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\torder to preserve your modifications in context.  With this\n+\toption, the conflicting local changes are automatically\n+\tstashed before the switch and reapplied afterwards.  If the\n+\tlocal changes do not overlap with the differences between\n+\tbranches, the switch proceeds without stashing.  If\n+\treapplying the stash results in conflicts, the entry is\n+\tsaved to the stash list so you can use `git stash pop` to\n+\trecover and `git stash drop` when done.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +216,16 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would save the local\n+changes in a stash entry and reset the working tree to allow switching:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Created autostash: 7a9afa3\n+Applied autostash.\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1d1667fa4c..1968e46ae9 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -845,83 +845,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n-\t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t}\n+\t\tif (ret == -1)\n+\t\t\treturn 1;\n \t}\n \n \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n@@ -930,9 +855,6 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\n \t\tdie(_(\"unable to write new index file\"));\n \n-\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n-\t\tshow_local_changes(&new_branch_info->commit->object, &opts->diff_options);\n-\n \treturn 0;\n }\n \n@@ -1157,6 +1079,55 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n \trelease_revisions(&revs);\n }\n \n+static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n+\t\t\t\t\t  struct branch_info *new_branch_info)\n+{\n+\tstruct tree_desc trees[2];\n+\tstruct tree *old_tree, *new_tree;\n+\tstruct unpack_trees_options topts;\n+\tstruct index_state tmp_index = INDEX_STATE_INIT(the_repository);\n+\tconst struct object_id *old_commit_oid;\n+\tint ret;\n+\n+\tif (!new_branch_info->commit)\n+\t\treturn 0;\n+\n+\told_commit_oid = old_branch_info->commit ?\n+\t\t&old_branch_info->commit->object.oid :\n+\t\tthe_hash_algo->empty_tree;\n+\told_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);\n+\tif (!old_tree)\n+\t\treturn 0;\n+\n+\tnew_tree = repo_get_commit_tree(the_repository,\n+\t\t\t\t\tnew_branch_info->commit);\n+\tif (!new_tree)\n+\t\treturn 0;\n+\tif (repo_parse_tree(the_repository, new_tree) < 0)\n+\t\treturn 0;\n+\n+\tmemset(&topts, 0, sizeof(topts));\n+\ttopts.head_idx = -1;\n+\ttopts.src_index = the_repository->index;\n+\ttopts.dst_index = &tmp_index;\n+\ttopts.initial_checkout = is_index_unborn(the_repository->index);\n+\ttopts.merge = 1;\n+\ttopts.update = 1;\n+\ttopts.dry_run = 1;\n+\ttopts.quiet = 1;\n+\ttopts.fn = twoway_merge;\n+\n+\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n+\t\t       old_tree->buffer, old_tree->size);\n+\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n+\t\t       new_tree->buffer, new_tree->size);\n+\n+\tret = unpack_trees(2, trees, &topts);\n+\tdiscard_index(&tmp_index);\n+\n+\treturn ret != 0;\n+}\n+\n static int switch_branches(const struct checkout_opts *opts,\n \t\t\t   struct branch_info *new_branch_info)\n {\n@@ -1165,6 +1136,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_ancestor = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1202,10 +1175,34 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_ancestor = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_ancestor = old_commit_shortname.buf;\n+\t}\n+\n+\tif (opts->merge) {\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\t\tif (checkout_would_clobber_changes(&old_branch_info,\n+\t\t\t\t\t\t   new_branch_info))\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_ancestor);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1215,8 +1212,28 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_ancestor);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex e79d612e57..1016d88e52 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label1, const char *label2,\n+\t\t\t\t      const char *label_ancestor)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label1 ? label1 : \"Updated upstream\";\n+\to.branch2 = label2 ? label2 : \"Stashed changes\";\n+\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label1, label2, label_ancestor);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/sequencer.c b/sequencer.c\nindex aafd0bc959..53e04d8a94 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4632,7 +4632,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      int silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4677,7 +4678,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4689,15 +4691,22 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, 0);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, 0);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, 1);\n+}\n+\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4708,6 +4717,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label1)\n+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n+\t\tif (label2)\n+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n+\t\tif (label_ancestor)\n+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4726,15 +4741,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\n@@ -4752,7 +4775,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4771,11 +4795,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4791,7 +4817,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label1, label2, label_ancestor);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4801,12 +4828,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel1, label2, label_ancestor);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 719684c8a9..68b94d86e3 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -227,11 +227,15 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..f4fea7bb7a 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,166 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..b061cc1b4a 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -342,7 +342,7 @@ test_expect_success 'merge --squash --autostash conflict does not attempt to app\n \t>unrelated &&\n \tgit add unrelated &&\n \ttest_must_fail git merge --squash c7 --autostash >out 2>err &&\n-\t! grep \"Applying autostash resulted in conflicts.\" err &&\n+\t! grep \"resulted in conflicts\" err &&\n \tgrep \"When finished, apply stashed changes with \\`git stash pop\\`\" out\n '\n \n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n\nbase-commit: dc6ecd5354dca88d51b6d6562777fc8fc10d77e1\n-- \ngitgitgadget\n"},{"id":"538981","messageId":"xmqqms0awcs8.fsf@gitster.g","threadId":"65220","inReplyTo":"953b5842-a4ae-40f6-8cae-c4f81239c903@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-14T17:17:59Z","receivedAt":"2026-03-14T17:18:03Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 12/03/2026 14:40, Junio C Hamano wrote:\n>> \n>> Perhaps doing it this way would make it more worth doing?\n>> \n>>   - At the beginning of branch switching, ask a new helper function\n>>     that takes the branch we are switching to as an argument this\n>>     question:\n>> \n>>     Do any paths that are different between the current branch and\n>>     the branch we are switching to have local (i.e., either in the\n>>     index or in the working tree) change [Yes/No]?\n>> \n>>   - When the answer is \"yes\", save the local changes to a new stash\n>>     entry, and clear the local changes from the index and from the\n>>     working tree.  If not, do not bother with stash at all.\n>\n> Can we avoid the extra check and stash if the user passed \"--autostash\" \n> and unpack_trees() fails because it would overwrite local changes in \n> merge_working_tree()?\n\nSorry, but I couldn't quite figure out what you are saying here.\n\nMy guess on one part of what it says is that an explicit\n\"--autostash\", we should stash without second guessing the user\n(i.e., avoid chedk and stash).  But then the latter part of the\nsentence \"and unpack_trees() fails ...\" do not quite parse.\n\nIf the user gave \"--autostash\" and we check with unpack_trees()\ndry-run and find out that a normal branch switch will be interfered\nby the local changes, then we would stash, but that check made by a\ndry-run unpack_trees() is not an \"extra\" check, so, that does not\nwork as a guess of what you are saying, either.\n\n>> If we can sell it as an improved implementation of \"-m\", we probably\n>> can lose some code that the current \"-m\" implementation uses to do\n>> its merge; we'd be instead using the \"unstash\" code paths.\n>\n> That would be nice but I think \"git checkout --recurse-submodules -m \n> <branch>\" currently updates submodules whereas \"git stash\" does not know \n> how to recurse submodules.\n\nHmph, I do not do submodules outside what we already have, and I\ncertainly do not do \"checkout --recurse-submodules\" with or without\n\"-m\" with local changes in our submodule.\n\nBut does \"git stash\" even need to know about recursing into\nsubmodules for this?  When checkout recurses into a submodule, that\ncheckout that is working in the repository of the submodule can\nhandle \"-m\" itself, which may stash the local changes made in the\nsubmodule, no?\n\n> It would be nice to teach \"git stash\" to recurse submodules but I don't \n> think it is completly straight forward as we'd need to store the object \n> id of the submodule's stash commit in the parent stash.\n\nNo, let's not add more commands that take \"--recurse-submodules\", if\nwe do not have to.\n\nThanks.\n\n"},{"id":"539006","messageId":"xmqqfr61vnf5.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v4.git.git.1773482375668.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-15T02:25:50Z","receivedAt":"2026-03-15T02:25:52Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When switching branches with \"git checkout -m\", local modifications\n> can block the switch.  Teach the -m flow to create a temporary stash\n> before switching and reapply it after.  On success, only \"Applied\n> autostash.\" is shown.  If reapplying causes conflicts, the stash is\n> kept and the user is told they can resolve and run \"git stash drop\",\n> or run \"git reset --hard\" and later \"git stash pop\" to recover their\n> changes.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>     checkout: 'autostash' for branch switching\n>     \n>     cc: Phillip Wood phillip.wood123@gmail.com\n\nI very much appreciate this, but somehow this \"cc:\" seems to have\nbeen ignored.\n\n>  Documentation/git-checkout.adoc |  56 +++++-----\n>  Documentation/git-switch.adoc   |  26 ++---\n>  builtin/checkout.c              | 179 +++++++++++++++++---------------\n>  builtin/stash.c                 |  30 ++++--\n>  sequencer.c                     |  67 +++++++++---\n>  sequencer.h                     |   4 +\n>  t/t3420-rebase-autostash.sh     |  24 +++--\n>  t/t7201-co.sh                   | 160 ++++++++++++++++++++++++++++\n>  t/t7600-merge.sh                |   4 +-\n>  xdiff-interface.c               |  12 +++\n>  xdiff-interface.h               |   1 +\n>  11 files changed, 412 insertions(+), 151 deletions(-)\n>\n> diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\n> index 43ccf47cf6..9d5f5c51ae 100644\n> --- a/Documentation/git-checkout.adoc\n> +++ b/Documentation/git-checkout.adoc\n> @@ -251,20 +251,17 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n>  \tare different between the current branch and the branch to\n>  \twhich you are switching, the command refuses to switch\n>  \tbranches in order to preserve your modifications in context.\n> -\tHowever, with this option, a three-way merge between the current\n> -\tbranch, your working tree contents, and the new branch\n> -\tis done, and you will be on the new branch.\n> -+\n> -When a merge conflict happens, the index entries for conflicting\n> -paths are left unmerged, and you need to resolve the conflicts\n> -and mark the resolved paths with `git add` (or `git rm` if the merge\n> -should result in deletion of the path).\n> +\tWith this option, the conflicting local changes are\n> +\tautomatically stashed before the switch and reapplied\n> +\tafterwards.  If the local changes do not overlap with the\n> +\tdifferences between branches, the switch proceeds without\n> +\tstashing.  If reapplying the stash results in conflicts, the\n> +\tentry is saved to the stash list so you can use `git stash\n> +\tpop` to recover and `git stash drop` when done.\n\nThe new conflict message in sequencer.c correctly points out that,\nif the user wants to use `git stash pop`, they would first need to\nclear the working tree (e.g. `git reset --hard`) because the\nconflicted changes are already there.\n\nThe documentation here is a bit too simplified and may give the\nimpression that `git stash pop` is the immediate next step when\nconflicts are present in the worktree, which is probably something\nwe want to avoid.\n\n>  ------------\n>  $ git checkout -m mytopic\n> -Auto-merging frotz\n> +Applied autostash.\n>  ------------\n\nI must be utterly confused, but the documentation _before_ this\npatch does not seem to match the reality.  In the following\nillustration, I am switching to another branch that has contents in\ntransport.c that is different from the version in HEAD:\n\n    $ git checkout master\n    $ echo >>transport.c\n    $ git checkout jk/transport-color-leakfix\n    error: Your local changes to the following files would be overwritten...\n        transport.c\n    Please commit your changes or stash ...\n    Aborting\n    $ git checkout -m jk/transport-color-leakfix\n    M\ttransport.c\n    Switched to branch 'jk/transport-color-leakfix'\n\nHmmmm.\n\nThe new behavior is silent about individual files, and instead\nreports \"Applied autostash.\"; even though it is consistent with how\nother commands may behave when given the \"--autostash\" option, I am\nnot sure if it is even necessary.\n\nEven in the documented version, the \"Auto-merging\" message is\npresumably given only to the paths that needed real content-level\nmerges, and the paths that went through the trivial tree-level\nmerges (i.e., paths that are different from HEAD and mytopic but\nwithout local changes) are not reported.  The behaviour I am seeing\ndoes not say anything to either trivially merged or really merged\npaths while branch switching is happening, and shows the local\nchanges at the end of merge_working_tree() by calling\nshow_local_changes(), which runs \"git diff --name-status\"\nin-process.\n\nIn either case, the stashing is a mere implementation detail of\ncarrying the local changes with you when you move to a different\nbranch, and moving with your local changes is what \"Switching\"\nbranch is about in this system, so I am not sure if we need to say\nanything more than the current \"Switched to branch 'mytopic'\" in the\nsuccessful case.\n\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 1d1667fa4c..1968e46ae9 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -17,7 +17,6 @@\n>  #include \"merge-ll.h\"\n>  #include \"lockfile.h\"\n>  #include \"mem-pool.h\"\n> -#include \"merge-ort-wrappers.h\"\n>  #include \"object-file.h\"\n>  #include \"object-name.h\"\n>  #include \"odb.h\"\n> @@ -30,6 +29,7 @@\n>  #include \"repo-settings.h\"\n>  #include \"resolve-undo.h\"\n>  #include \"revision.h\"\n> +#include \"sequencer.h\"\n>  #include \"setup.h\"\n>  #include \"submodule.h\"\n>  #include \"symlinks.h\"\n> @@ -845,83 +845,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>  \n>  \t\tret = unpack_trees(2, trees, &topts);\n>  \t\tclear_unpack_trees_porcelain(&topts);\n> -\t\tif (ret == -1) {\n> -...\n> -\t\t}\n> +\t\tif (ret == -1)\n> +\t\t\treturn 1;\n>  \t}\n>  \n>  \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n> @@ -930,9 +855,6 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>  \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\n>  \t\tdie(_(\"unable to write new index file\"));\n>  \n> -\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n> -\t\tshow_local_changes(&new_branch_info->commit->object, &opts->diff_options);\n\nAh, this is what you lost.  I do not see a reason why we would want\nto lose it.\n\n> +static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n> +\t\t\t\t\t  struct branch_info *new_branch_info)\n> +{\n> +...\n> +\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n> +\t\t       old_tree->buffer, old_tree->size);\n> +\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n> +\t\t       new_tree->buffer, new_tree->size);\n> +\n> +\tret = unpack_trees(2, trees, &topts);\n> +\tdiscard_index(&tmp_index);\n\nIn my original outline, I did suggest \n\n - check if the local changes overlap with paths that differ between\n   HEAD and the new branch, and if not, we need no stashing;\n   otherwise stash.\n\n - then switch branches with two-way merge\n\n - then unstash.\n\nBut my untold assumption was that the initial check was done by\nrunning a moral equivalent of \"git diff --name-only HEAD <branch>\"\nto obtain the list of paths that differ between branches, and\na \"git diff HEAD\" to obtain the list of paths with local changes.\n\nIf you are using the real branch switching machinery in the\n\"dry-run\" mode, then it is a waste to discard when we find out that\nthere is no overlap.  So if I knew you would use the unpack-trees in\nthe dry-run mode, instead of two diffs, the outline would have been\nmore like\n\n - Try unpack-trees in dry-run mode to see if there would get any\n   failure from overlap.\n\n - If unpack-trees would succeed, we already have the right contents\n   in the index after the above dry-run.  We did not touch the\n   working tree so far, but if we kept track of which paths we would\n   have written out to the working tree during our dry-run, we can\n   write them out of the index at this point and we are done (we of\n   course need to update HEAD to point at the new branch, give the\n   \"Switched to branch 'foo'\" message, etc., but the real work of\n   switching branches is done).\n\n - If not, discard the dry-run index, stash, switch branches the\n   usual way, and unstash.\n\nwhich might be more performant in a large tree.\n\nHaving said all that, what we see above, while running the same\nunpack-trees once in dry-run mode and another for real, may be\nperformant enough and complicating the dry-run code might not be\nworth it.\n\n> @@ -1165,6 +1136,8 @@ static int switch_branches(const struct checkout_opts *opts,\n>  \tstruct object_id rev;\n>  \tint flag, writeout_error = 0;\n>  \tint do_merge = 1;\n> +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n> +\tconst char *stash_label_ancestor = NULL;\n>  \n>  \ttrace2_cmd_mode(\"branch\");\n>  \n> @@ -1202,10 +1175,34 @@ static int switch_branches(const struct checkout_opts *opts,\n>  \t\t\tdo_merge = 0;\n>  \t}\n>  \n> +\tif (old_branch_info.name)\n> +\t\tstash_label_ancestor = old_branch_info.name;\n> +\telse if (old_branch_info.commit) {\n> +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n> +\t\t\t\t\t &old_branch_info.commit->object.oid,\n> +\t\t\t\t\t DEFAULT_ABBREV);\n> +\t\tstash_label_ancestor = old_commit_shortname.buf;\n> +\t}\n> +\n> +\tif (opts->merge) {\n> +\t\tif (repo_read_index(the_repository) < 0)\n> +\t\t\tdie(_(\"index file corrupt\"));\n> +\t\tif (checkout_would_clobber_changes(&old_branch_info,\n> +\t\t\t\t\t\t   new_branch_info))\n> +\t\t\tcreate_autostash_ref_silent(the_repository,\n> +\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n> +\t}\n> +\n>  \tif (do_merge) {\n>  \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>  \t\tif (ret) {\n> +\t\t\tapply_autostash_ref_with_labels(the_repository,\n> +\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n> +\t\t\t\t\t\t       new_branch_info->name,\n> +\t\t\t\t\t\t       \"local\",\n> +\t\t\t\t\t\t       stash_label_ancestor);\n>  \t\t\tbranch_info_release(&old_branch_info);\n> +\t\t\tstrbuf_release(&old_commit_shortname);\n>  \t\t\treturn ret;\n>  \t\t}\n>  \t}\n\nNice changes to preserve the conflict marker labels.\n\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index e79d612e57..1016d88e52 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n\nThis can and should become a separate patch that is a preliminary\npreparation step, [1/2], with its own documentation and test, only\nabout teaching \"git stash\" command a set of new options to use\ncustom conflict labels, I think.  And on top of it, patch [2/2] that\nupdates \"checkout -m\" to use the improved \"git stash\" with custom\nlabels support is written.  It would give us a clean logical\nprogression of the series.\n\n> diff --git a/sequencer.c b/sequencer.c\n> index aafd0bc959..53e04d8a94 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4632,7 +4632,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n>  \n>  static void create_autostash_internal(struct repository *r,\n>  \t\t\t\t      const char *path,\n> -\t\t\t\t      const char *refname)\n> +\t\t\t\t      const char *refname,\n> +\t\t\t\t      int silent)\n>  {\n>  \tstruct strbuf buf = STRBUF_INIT;\n>  \tstruct lock_file lock_file = LOCK_INIT;\n> @@ -4677,7 +4678,8 @@ static void create_autostash_internal(struct repository *r,\n>  \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n>  \t\t}\n>  \n> -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n> +\t\tif (!silent)\n> +\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n>  \t\tif (reset_head(r, &ropts) < 0)\n>  \t\t\tdie(_(\"could not reset --hard\"));\n>  \t\tdiscard_index(r->index);\n> @@ -4689,15 +4691,22 @@ static void create_autostash_internal(struct repository *r,\n>  \n>  void create_autostash(struct repository *r, const char *path)\n>  {\n> -\tcreate_autostash_internal(r, path, NULL);\n> +\tcreate_autostash_internal(r, path, NULL, 0);\n>  }\n>  \n>  void create_autostash_ref(struct repository *r, const char *refname)\n>  {\n> -\tcreate_autostash_internal(r, NULL, refname);\n> +\tcreate_autostash_internal(r, NULL, refname, 0);\n>  }\n>  \n> -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n> +void create_autostash_ref_silent(struct repository *r, const char *refname)\n> +{\n> +\tcreate_autostash_internal(r, NULL, refname, 1);\n> +}\n\nChanges around here to optionally squelch message(s) is better\npresented as yet another preliminary preparation step, I think.\n\n> @@ -4752,7 +4775,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n>  \t}\n>  \tstrbuf_trim(&stash_oid);\n>  \n> -\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n> +\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n> +\t\t\t\t      NULL, NULL, NULL);\n>  \n>  \tunlink(path);\n>  \tstrbuf_release(&stash_oid);\n> @@ -4771,11 +4795,13 @@ int apply_autostash(const char *path)\n>  \n>  int apply_autostash_oid(const char *stash_oid)\n>  {\n> -\treturn apply_save_autostash_oid(stash_oid, 1);\n> +\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n>  }\n>  \n>  static int apply_save_autostash_ref(struct repository *r, const char *refname,\n> -\t\t\t\t    int attempt_apply)\n> +\t\t\t\t    int attempt_apply,\n> +\t\t\t\t    const char *label1, const char *label2,\n> +\t\t\t\t    const char *label_ancestor)\n>  {\n>  \tstruct object_id stash_oid;\n>  \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n> @@ -4791,7 +4817,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>  \t\treturn error(_(\"autostash reference is a symref\"));\n>  \n>  \toid_to_hex_r(stash_oid_hex, &stash_oid);\n> -\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n> +\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n> +\t\t\t\t       label1, label2, label_ancestor);\n>  \n>  \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n>  \t\t\t&stash_oid, REF_NO_DEREF);\n>\n> @@ -4801,12 +4828,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>  \n>  int save_autostash_ref(struct repository *r, const char *refname)\n>  {\n> -\treturn apply_save_autostash_ref(r, refname, 0);\n> +\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n>  }\n>  \n>  int apply_autostash_ref(struct repository *r, const char *refname)\n>  {\n> -\treturn apply_save_autostash_ref(r, refname, 1);\n> +\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n> +}\n> +\n> +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n> +\t\t\t\t    const char *label1, const char *label2,\n> +\t\t\t\t    const char *label_ancestor)\n> +{\n> +\treturn apply_save_autostash_ref(r, refname, 1,\n> +\t\t\t\t\tlabel1, label2, label_ancestor);\n>  }\n>  \n>  static int checkout_onto(struct repository *r, struct replay_opts *opts,\n\nSo are these, to teach the machinery to take optional conflict\nmarker labels.\n\nThanks.\n"},{"id":"539023","messageId":"pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v4.git.git.1773482375668.gitgitgadget@gmail.com","subject":"[PATCH v5 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-15T11:19:09Z","receivedAt":"2026-03-15T11:19:16Z","isPatch":true,"body":"cc: Phillip Wood phillip.wood123@gmail.com\n\nHarald Nordgren (4):\n  stash: add --ours-label, --theirs-label, --base-label for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  27 ++---\n builtin/checkout.c              | 180 ++++++++++++++++++--------------\n builtin/stash.c                 |  32 ++++--\n sequencer.c                     |  67 +++++++++---\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++--\n t/t3903-stash.sh                |  18 ++++\n t/t7201-co.sh                   | 160 ++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 13 files changed, 447 insertions(+), 149 deletions(-)\n\n\nbase-commit: dc6ecd5354dca88d51b6d6562777fc8fc10d77e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v5\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v4:\n\n -:  ---------- > 1:  00d8920498 stash: add --ours-label, --theirs-label, --base-label for apply\n -:  ---------- > 2:  5d176f1700 sequencer: allow create_autostash to run silently\n -:  ---------- > 3:  3d6829438a sequencer: teach autostash apply to take optional conflict marker labels\n 1:  5d49c0031a ! 4:  7f3735d40e checkout: -m (--merge) uses autostash when switching branches\n     @@ Documentation/git-checkout.adoc: working tree, by copying them from elsewhere, e\n      +\tafterwards.  If the local changes do not overlap with the\n      +\tdifferences between branches, the switch proceeds without\n      +\tstashing.  If reapplying the stash results in conflicts, the\n     -+\tentry is saved to the stash list so you can use `git stash\n     -+\tpop` to recover and `git stash drop` when done.\n     ++\tentry is saved to the stash list.  Resolve the conflicts\n     ++\tand run `git stash drop` when done, or clear the working\n     ++\ttree (e.g. with `git reset --hard`) before running `git stash\n     ++\tpop` later to re-apply your changes.\n       +\n       When checking out paths from the index, this option lets you recreate\n       the conflicted merge in the specified paths.  This option cannot be\n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n       \n      -You can give the `-m` flag to the command, which would try a\n      -three-way merge:\n     -+You can give the `-m` flag to the command, which would save the local\n     -+changes in a stash entry and reset the working tree to allow switching:\n     ++You can give the `-m` flag to the command, which would carry your local\n     ++changes to the new branch:\n       \n       ------------\n       $ git checkout -m mytopic\n      -Auto-merging frotz\n     -+Applied autostash.\n     ++Switched to branch 'mytopic'\n       ------------\n       \n      -After this three-way merge, the local modifications are _not_\n     @@ Documentation/git-switch.adoc: variable.\n      +\tlocal changes do not overlap with the differences between\n      +\tbranches, the switch proceeds without stashing.  If\n      +\treapplying the stash results in conflicts, the entry is\n     -+\tsaved to the stash list so you can use `git stash pop` to\n     -+\trecover and `git stash drop` when done.\n     ++\tsaved to the stash list.  Resolve the conflicts and run\n     ++\t`git stash drop` when done, or clear the working tree\n     ++\t(e.g. with `git reset --hard`) before running `git stash pop`\n     ++\tlater to re-apply your changes.\n       \n       `--conflict=<style>`::\n       \tThe same as `--merge` option above, but changes the way the\n     @@ Documentation/git-switch.adoc: $ git switch mytopic\n       \n      -You can give the `-m` flag to the command, which would try a three-way\n      -merge:\n     -+You can give the `-m` flag to the command, which would save the local\n     -+changes in a stash entry and reset the working tree to allow switching:\n     ++You can give the `-m` flag to the command, which would carry your local\n     ++changes to the new branch:\n       \n       ------------\n       $ git switch -m mytopic\n      -Auto-merging frotz\n     -+Created autostash: 7a9afa3\n     -+Applied autostash.\n     ++Switched to branch 'mytopic'\n       ------------\n       \n      -After this three-way merge, the local modifications are _not_\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n       \t}\n       \n       \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n     -@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     - \tif (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))\n     - \t\tdie(_(\"unable to write new index file\"));\n     - \n     --\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n     --\t\tshow_local_changes(&new_branch_info->commit->object, &opts->diff_options);\n     --\n     - \treturn 0;\n     - }\n     - \n      @@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n       \trelease_revisions(&revs);\n       }\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tstruct object_id rev;\n       \tint flag, writeout_error = 0;\n       \tint do_merge = 1;\n     ++\tint created_autostash = 0;\n      +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n      +\tconst char *stash_label_ancestor = NULL;\n       \n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\tif (repo_read_index(the_repository) < 0)\n      +\t\t\tdie(_(\"index file corrupt\"));\n      +\t\tif (checkout_would_clobber_changes(&old_branch_info,\n     -+\t\t\t\t\t\t   new_branch_info))\n     ++\t\t\t\t\t\t   new_branch_info)) {\n      +\t\t\tcreate_autostash_ref_silent(the_repository,\n      +\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n     ++\t\t\tcreated_autostash = 1;\n     ++\t\t}\n      +\t}\n      +\n       \tif (do_merge) {\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\tif (repo_read_index(the_repository) < 0)\n      +\t\tdie(_(\"index file corrupt\"));\n      +\n     -+\tif (!opts->discard_changes && !opts->quiet && new_branch_info->commit)\n     ++\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n     ++\t    new_branch_info->commit)\n      +\t\tshow_local_changes(&new_branch_info->commit->object,\n      +\t\t\t\t   &opts->diff_options);\n      +\n     @@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char\n       \treturn ret;\n      \n       ## sequencer.c ##\n     -@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n     - \n     - static void create_autostash_internal(struct repository *r,\n     - \t\t\t\t      const char *path,\n     --\t\t\t\t      const char *refname)\n     -+\t\t\t\t      const char *refname,\n     -+\t\t\t\t      int silent)\n     - {\n     - \tstruct strbuf buf = STRBUF_INIT;\n     - \tstruct lock_file lock_file = LOCK_INIT;\n     -@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     - \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n     - \t\t}\n     - \n     --\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n     -+\t\tif (!silent)\n     -+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n     - \t\tif (reset_head(r, &ropts) < 0)\n     - \t\t\tdie(_(\"could not reset --hard\"));\n     - \t\tdiscard_index(r->index);\n     -@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     - \n     - void create_autostash(struct repository *r, const char *path)\n     - {\n     --\tcreate_autostash_internal(r, path, NULL);\n     -+\tcreate_autostash_internal(r, path, NULL, 0);\n     - }\n     - \n     - void create_autostash_ref(struct repository *r, const char *refname)\n     - {\n     --\tcreate_autostash_internal(r, NULL, refname);\n     -+\tcreate_autostash_internal(r, NULL, refname, 0);\n     - }\n     - \n     --static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     -+void create_autostash_ref_silent(struct repository *r, const char *refname)\n     -+{\n     -+\tcreate_autostash_internal(r, NULL, refname, 1);\n     -+}\n     -+\n     -+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor)\n     - {\n     - \tstruct child_process child = CHILD_PROCESS_INIT;\n     - \tint ret = 0;\n     -@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     - \t\tchild.no_stderr = 1;\n     - \t\tstrvec_push(&child.args, \"stash\");\n     - \t\tstrvec_push(&child.args, \"apply\");\n     -+\t\tif (label1)\n     -+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n     -+\t\tif (label2)\n     -+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n     -+\t\tif (label_ancestor)\n     -+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n     - \t\tstrvec_push(&child.args, stash_oid);\n     - \t\tret = run_command(&child);\n     - \t}\n     -@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n       \t\tstrvec_push(&store.args, stash_oid);\n       \t\tif (run_command(&store))\n       \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t}\n       \n       \treturn ret;\n     -@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n     - \t}\n     - \tstrbuf_trim(&stash_oid);\n     - \n     --\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n     -+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n     -+\t\t\t\t      NULL, NULL, NULL);\n     - \n     - \tunlink(path);\n     - \tstrbuf_release(&stash_oid);\n     -@@ sequencer.c: int apply_autostash(const char *path)\n     - \n     - int apply_autostash_oid(const char *stash_oid)\n     - {\n     --\treturn apply_save_autostash_oid(stash_oid, 1);\n     -+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n     - }\n     - \n     - static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     --\t\t\t\t    int attempt_apply)\n     -+\t\t\t\t    int attempt_apply,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor)\n     - {\n     - \tstruct object_id stash_oid;\n     - \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n     -@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     - \t\treturn error(_(\"autostash reference is a symref\"));\n     - \n     - \toid_to_hex_r(stash_oid_hex, &stash_oid);\n     --\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n     -+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n     -+\t\t\t\t       label1, label2, label_ancestor);\n     - \n     - \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n     - \t\t\t&stash_oid, REF_NO_DEREF);\n     -@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     - \n     - int save_autostash_ref(struct repository *r, const char *refname)\n     - {\n     --\treturn apply_save_autostash_ref(r, refname, 0);\n     -+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n     - }\n     - \n     - int apply_autostash_ref(struct repository *r, const char *refname)\n     - {\n     --\treturn apply_save_autostash_ref(r, refname, 1);\n     -+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n     -+}\n     -+\n     -+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor)\n     -+{\n     -+\treturn apply_save_autostash_ref(r, refname, 1,\n     -+\t\t\t\t\tlabel1, label2, label_ancestor);\n     - }\n     - \n     - static int checkout_onto(struct repository *r, struct replay_opts *opts,\n     -\n     - ## sequencer.h ##\n     -@@ sequencer.h: void commit_post_rewrite(struct repository *r,\n     - \n     - void create_autostash(struct repository *r, const char *path);\n     - void create_autostash_ref(struct repository *r, const char *refname);\n     -+void create_autostash_ref_silent(struct repository *r, const char *refname);\n     - int save_autostash(const char *path);\n     - int save_autostash_ref(struct repository *r, const char *refname);\n     - int apply_autostash(const char *path);\n     - int apply_autostash_oid(const char *stash_oid);\n     - int apply_autostash_ref(struct repository *r, const char *refname);\n     -+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor);\n     - \n     - #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n     - #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n      \n       ## t/t3420-rebase-autostash.sh ##\n      @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n       \tgit reset --hard &&\n      \n       ## t/t7600-merge.sh ##\n     -@@ t/t7600-merge.sh: test_expect_success 'merge --squash --autostash conflict does not attempt to app\n     - \t>unrelated &&\n     - \tgit add unrelated &&\n     - \ttest_must_fail git merge --squash c7 --autostash >out 2>err &&\n     --\t! grep \"Applying autostash resulted in conflicts.\" err &&\n     -+\t! grep \"resulted in conflicts\" err &&\n     - \tgrep \"When finished, apply stashed changes with \\`git stash pop\\`\" out\n     - '\n     - \n      @@ t/t7600-merge.sh: test_expect_success 'merge with conflicted --autostash changes' '\n       \tgit diff >expect &&\n       \ttest_when_finished \"test_might_fail git stash drop\" &&\n       \tgit merge --autostash c3 2>err &&\n      -\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n     -+\ttest_grep \"resulted in conflicts\" err &&\n     ++\ttest_grep \"your local changes resulted in conflicts\" err &&\n       \tgit show HEAD:file >merge-result &&\n       \ttest_cmp result.1-9 merge-result &&\n       \tgit stash show -p >actual &&\n\n-- \ngitgitgadget\n"},{"id":"539024","messageId":"00d8920498fcc5dff2989f869fdaac2ed9f42d17.1773573553.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com","subject":"[PATCH v5 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-15T11:19:10Z","receivedAt":"2026-03-15T11:19:18Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              |  2 +-\n t/t3903-stash.sh             | 18 ++++++++++++++++++\n 3 files changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 235d57ddd8..43650a3c1a 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -197,6 +197,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--ours-label=<label>`::\n+`--theirs-label=<label>`::\n+`--base-label=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--base-label` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex e79d612e57..252e4df3a9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..dd47c1322a 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,24 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\techo base >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m base &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\techo upstream >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m upstream &&\n+\t\ttest_must_fail git stash apply --ours-label=UP --theirs-label=STASH &&\n+\t\tgrep \"^<<<<<<< UP\" file &&\n+\t\tgrep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n-- \ngitgitgadget\n\n"},{"id":"539025","messageId":"5d176f1700ce1f8e8f38af1af9ce8fc5823dc4c8.1773573553.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com","subject":"[PATCH v5 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-15T11:19:11Z","receivedAt":"2026-03-15T11:19:22Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.  Use stderr for\nthe message when not silent.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex aafd0bc959..eebefd731b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4632,7 +4632,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      int silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4677,7 +4678,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4689,12 +4691,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, 0);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, 0);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex 719684c8a9..0b09d6799b 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -227,6 +227,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"539026","messageId":"3d6829438a561d79e6b8996297e3b6505f07ac3d.1773573553.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com","subject":"[PATCH v5 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-15T11:19:12Z","receivedAt":"2026-03-15T11:19:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label1, label2, and label_ancestor parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --ours-label/--theirs-label/--base-label\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex eebefd731b..080a25820a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4704,7 +4704,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4715,6 +4717,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label1)\n+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n+\t\tif (label2)\n+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n+\t\tif (label_ancestor)\n+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4759,7 +4767,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4778,11 +4787,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4798,7 +4809,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label1, label2, label_ancestor);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4808,12 +4820,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel1, label2, label_ancestor);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 0b09d6799b..68b94d86e3 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -233,6 +233,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"539027","messageId":"7f3735d40ef9045595085064340676cc6058e627.1773573553.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com","subject":"[PATCH v5 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-15T11:19:13Z","receivedAt":"2026-03-15T11:19:25Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-switch.adoc   |  27 ++---\n builtin/checkout.c              | 180 ++++++++++++++++++--------------\n builtin/stash.c                 |  30 ++++--\n sequencer.c                     |  18 +++-\n t/t3420-rebase-autostash.sh     |  24 +++--\n t/t7201-co.sh                   | 160 ++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 10 files changed, 376 insertions(+), 136 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..18022e610e 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If the stash application results in conflicts, they are not\n+resolved and the stash is saved to the stash list:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..3147023dc3 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -126,15 +126,16 @@ variable.\n \tIf you have local modifications to one or more files that are\n \tdifferent between the current branch and the branch to which\n \tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\torder to preserve your modifications in context.  With this\n+\toption, the conflicting local changes are automatically\n+\tstashed before the switch and reapplied afterwards.  If the\n+\tlocal changes do not overlap with the differences between\n+\tbranches, the switch proceeds without stashing.  If\n+\treapplying the stash results in conflicts, the entry is\n+\tsaved to the stash list.  Resolve the conflicts and run\n+\t`git stash drop` when done, or clear the working tree\n+\t(e.g. with `git reset --hard`) before running `git stash pop`\n+\tlater to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1d1667fa4c..db93eabd9b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -845,83 +845,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n-\t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t}\n+\t\tif (ret == -1)\n+\t\t\treturn 1;\n \t}\n \n \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n@@ -1157,6 +1082,55 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n \trelease_revisions(&revs);\n }\n \n+static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n+\t\t\t\t\t  struct branch_info *new_branch_info)\n+{\n+\tstruct tree_desc trees[2];\n+\tstruct tree *old_tree, *new_tree;\n+\tstruct unpack_trees_options topts;\n+\tstruct index_state tmp_index = INDEX_STATE_INIT(the_repository);\n+\tconst struct object_id *old_commit_oid;\n+\tint ret;\n+\n+\tif (!new_branch_info->commit)\n+\t\treturn 0;\n+\n+\told_commit_oid = old_branch_info->commit ?\n+\t\t&old_branch_info->commit->object.oid :\n+\t\tthe_hash_algo->empty_tree;\n+\told_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);\n+\tif (!old_tree)\n+\t\treturn 0;\n+\n+\tnew_tree = repo_get_commit_tree(the_repository,\n+\t\t\t\t\tnew_branch_info->commit);\n+\tif (!new_tree)\n+\t\treturn 0;\n+\tif (repo_parse_tree(the_repository, new_tree) < 0)\n+\t\treturn 0;\n+\n+\tmemset(&topts, 0, sizeof(topts));\n+\ttopts.head_idx = -1;\n+\ttopts.src_index = the_repository->index;\n+\ttopts.dst_index = &tmp_index;\n+\ttopts.initial_checkout = is_index_unborn(the_repository->index);\n+\ttopts.merge = 1;\n+\ttopts.update = 1;\n+\ttopts.dry_run = 1;\n+\ttopts.quiet = 1;\n+\ttopts.fn = twoway_merge;\n+\n+\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n+\t\t       old_tree->buffer, old_tree->size);\n+\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n+\t\t       new_tree->buffer, new_tree->size);\n+\n+\tret = unpack_trees(2, trees, &topts);\n+\tdiscard_index(&tmp_index);\n+\n+\treturn ret != 0;\n+}\n+\n static int switch_branches(const struct checkout_opts *opts,\n \t\t\t   struct branch_info *new_branch_info)\n {\n@@ -1165,6 +1139,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_ancestor = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1202,10 +1179,36 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_ancestor = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_ancestor = old_commit_shortname.buf;\n+\t}\n+\n+\tif (opts->merge) {\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\t\tif (checkout_would_clobber_changes(&old_branch_info,\n+\t\t\t\t\t\t   new_branch_info)) {\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n+\t\t\tcreated_autostash = 1;\n+\t\t}\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_ancestor);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1215,8 +1218,29 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_ancestor);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 252e4df3a9..ab9c1359b1 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label1, const char *label2,\n+\t\t\t\t      const char *label_ancestor)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label1 ? label1 : \"Updated upstream\";\n+\to.branch2 = label2 ? label2 : \"Stashed changes\";\n+\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label1, label2, label_ancestor);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/sequencer.c b/sequencer.c\nindex 080a25820a..53e04d8a94 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4741,15 +4741,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..f4fea7bb7a 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,166 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"539130","messageId":"564a8207-6a96-4f42-b0bc-807edafff330@gmail.com","threadId":"65220","inReplyTo":"xmqqms0awcs8.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-16T16:36:10Z","receivedAt":"2026-03-16T16:36:14Z","isPatch":true,"body":"On 14/03/2026 17:17, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> On 12/03/2026 14:40, Junio C Hamano wrote:\n>>>\n>>> Perhaps doing it this way would make it more worth doing?\n>>>\n>>>    - At the beginning of branch switching, ask a new helper function\n>>>      that takes the branch we are switching to as an argument this\n>>>      question:\n>>>\n>>>      Do any paths that are different between the current branch and\n>>>      the branch we are switching to have local (i.e., either in the\n>>>      index or in the working tree) change [Yes/No]?\n>>>\n>>>    - When the answer is \"yes\", save the local changes to a new stash\n>>>      entry, and clear the local changes from the index and from the\n>>>      working tree.  If not, do not bother with stash at all.\n>>\n>> Can we avoid the extra check and stash if the user passed \"--autostash\"\n>> and unpack_trees() fails because it would overwrite local changes in\n>> merge_working_tree()?\n> \n> Sorry, but I couldn't quite figure out what you are saying here.\n> \n> My guess on one part of what it says is that an explicit\n> \"--autostash\", we should stash without second guessing the user\n> (i.e., avoid chedk and stash).  But then the latter part of the\n> sentence \"and unpack_trees() fails ...\" do not quite parse.\n> \n> If the user gave \"--autostash\" and we check with unpack_trees()\n> dry-run and find out that a normal branch switch will be interfered\n> by the local changes, then we would stash, but that check made by a\n> dry-run unpack_trees() is not an \"extra\" check, so, that does not\n> work as a guess of what you are saying, either.\n\nWhy is the dry-run of unpack_trees() not an extra check? I was assuming \nthat it was because we do the dry-run and then do it for real after \npossibly stashing any local changes. That's why I was wondering if we \ncould avoid the dry-run by creating the stash if the non-dry-run \nunpack_trees() failed. Looking at the unpack_trees() implementation it \ncan fail for a variety of reasons, only some (one?) of which can be \naddressed by stashing local changes but there does not seem to be a way \nfor the caller to determine what caused it to fail.\n\n>>> If we can sell it as an improved implementation of \"-m\", we probably\n>>> can lose some code that the current \"-m\" implementation uses to do\n>>> its merge; we'd be instead using the \"unstash\" code paths.\n>>\n>> That would be nice but I think \"git checkout --recurse-submodules -m\n>> <branch>\" currently updates submodules whereas \"git stash\" does not know\n>> how to recurse submodules.\n> \n> Hmph, I do not do submodules outside what we already have, and I\n> certainly do not do \"checkout --recurse-submodules\" with or without\n> \"-m\" with local changes in our submodule.\n> \n> But does \"git stash\" even need to know about recursing into\n> submodules for this?  When checkout recurses into a submodule, that\n> checkout that is working in the repository of the submodule can\n> handle \"-m\" itself, which may stash the local changes made in the\n> submodule, no?\n\nOh, because this all happens in a single command then yes, I think we \ncan. When I wrote that I'd been thinking about a recent question about \nrebase not recursing submodules on discord and what it would take to \nmake \"git rebase --recurse-submodules --autostash\" work. There we need \nto be able to retrive the stash in a different process to the one that \ncreated it so we need some way of tracking the stashed changes in each \nsubmodule.\n\nIt turns out I'd misremembered what \"git checkout -m \n--recurse-submodules\" does at the moment - after testing it, it seems to \nsimply nuke an uncommitted submodule changes rather than merging them.\n\nThanks\n\nPhillip\n\n>> It would be nice to teach \"git stash\" to recurse submodules but I don't\n>> think it is completly straight forward as we'd need to store the object\n>> id of the submodule's stash commit in the parent stash.\n> \n> No, let's not add more commands that take \"--recurse-submodules\", if\n> we do not have to.\n> \n> Thanks.\n> \n\n"},{"id":"539154","messageId":"xmqqcy13o81b.fsf@gitster.g","threadId":"65220","inReplyTo":"564a8207-6a96-4f42-b0bc-807edafff330@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-16T20:04:32Z","receivedAt":"2026-03-16T20:04:34Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Why is the dry-run of unpack_trees() not an extra check? I was assuming \n> that it was because we do the dry-run and then do it for real after \n> possibly stashing any local changes. That's why I was wondering if we \n> could avoid the dry-run by creating the stash if the non-dry-run \n> unpack_trees() failed.\n\nAh, I didn't even think about that possibility.\n\nTry to unpack anyway, and if unpack_trees() branch switching\nsucceeds, we are done.  Otherwise, we can trust that unpack_trees()\ndid not do _anything_ to the index or the working tree files, so we\ncan create the stash at that time.\n\nMakes sense.\n\n> It turns out I'd misremembered what \"git checkout -m \n> --recurse-submodules\" does at the moment - after testing it, it seems to \n> simply nuke an uncommitted submodule changes rather than merging them.\n\nMakes sense.\n"},{"id":"539202","messageId":"pull.2234.v6.git.git.1773740139.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v5.git.git.1773573553.gitgitgadget@gmail.com","subject":"[PATCH v6 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T09:35:35Z","receivedAt":"2026-03-17T09:35:41Z","isPatch":true,"body":"cc: Phillip Wood phillip.wood123@gmail.com\n\nHarald Nordgren (4):\n  stash: add --ours-label, --theirs-label, --base-label for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 ++++++------\n Documentation/git-stash.adoc    |  11 ++-\n Documentation/git-switch.adoc   |  27 +++---\n builtin/checkout.c              | 137 ++++++++++++---------------\n builtin/stash.c                 |  32 +++++--\n sequencer.c                     |  67 +++++++++----\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++--\n t/t3903-stash.sh                |  18 ++++\n t/t7201-co.sh                   | 160 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 13 files changed, 403 insertions(+), 150 deletions(-)\n\n\nbase-commit: ca1db8a0f7dc0dbea892e99f5b37c5fe5861be71\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v6\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v5:\n\n 1:  00d8920498 = 1:  cd9c64ba60 stash: add --ours-label, --theirs-label, --base-label for apply\n 2:  5d176f1700 = 2:  d572c4bb7d sequencer: allow create_autostash to run silently\n 3:  3d6829438a = 3:  80a98116fc sequencer: teach autostash apply to take optional conflict marker labels\n 4:  7f3735d40e ! 4:  7ecb0835b7 checkout: -m (--merge) uses autostash when switching branches\n     @@ builtin/checkout.c\n       #include \"submodule.h\"\n       #include \"symlinks.h\"\n      @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \tstruct tree *new_tree;\n       \n     + \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n     +-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n     ++\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n     ++\t\trollback_lock_file(&lock_file);\n     + \t\treturn error(_(\"index file corrupt\"));\n     ++\t}\n     + \n     + \tresolve_undo_clear_index(the_repository->index);\n     + \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n     +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \t} else {\n     + \t\tnew_tree = repo_get_commit_tree(the_repository,\n     + \t\t\t\t\t\tnew_branch_info->commit);\n     +-\t\tif (!new_tree)\n     ++\t\tif (!new_tree) {\n     ++\t\t\trollback_lock_file(&lock_file);\n     + \t\t\treturn error(_(\"unable to read tree (%s)\"),\n     + \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n     ++\t\t}\n     + \t}\n     + \tif (opts->discard_changes) {\n     + \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n     +-\t\tif (ret)\n     ++\t\tif (ret) {\n     ++\t\t\trollback_lock_file(&lock_file);\n     + \t\t\treturn ret;\n     ++\t\t}\n     + \t} else {\n     + \t\tstruct tree_desc trees[2];\n     + \t\tstruct tree *tree;\n     +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n     + \n     + \t\tif (unmerged_index(the_repository->index)) {\n     ++\t\t\trollback_lock_file(&lock_file);\n     + \t\t\terror(_(\"you need to resolve your current index first\"));\n     + \t\t\treturn 1;\n     + \t\t}\n     +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n       \t\tret = unpack_trees(2, trees, &topts);\n       \t\tclear_unpack_trees_porcelain(&topts);\n     --\t\tif (ret == -1) {\n     + \t\tif (ret == -1) {\n      -\t\t\t/*\n      -\t\t\t * Unpack couldn't do a trivial merge; either\n      -\t\t\t * give up or do a real merge, depending on\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      -\t\t\tstrbuf_release(&old_commit_shortname);\n      -\t\t\tif (ret)\n      -\t\t\t\treturn ret;\n     --\t\t}\n     -+\t\tif (ret == -1)\n     ++\t\t\trollback_lock_file(&lock_file);\n      +\t\t\treturn 1;\n     + \t\t}\n       \t}\n       \n     - \tif (!cache_tree_fully_valid(the_repository->index->cache_tree))\n     -@@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne\n     - \trelease_revisions(&revs);\n     - }\n     - \n     -+static int checkout_would_clobber_changes(struct branch_info *old_branch_info,\n     -+\t\t\t\t\t  struct branch_info *new_branch_info)\n     -+{\n     -+\tstruct tree_desc trees[2];\n     -+\tstruct tree *old_tree, *new_tree;\n     -+\tstruct unpack_trees_options topts;\n     -+\tstruct index_state tmp_index = INDEX_STATE_INIT(the_repository);\n     -+\tconst struct object_id *old_commit_oid;\n     -+\tint ret;\n     -+\n     -+\tif (!new_branch_info->commit)\n     -+\t\treturn 0;\n     -+\n     -+\told_commit_oid = old_branch_info->commit ?\n     -+\t\t&old_branch_info->commit->object.oid :\n     -+\t\tthe_hash_algo->empty_tree;\n     -+\told_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);\n     -+\tif (!old_tree)\n     -+\t\treturn 0;\n     -+\n     -+\tnew_tree = repo_get_commit_tree(the_repository,\n     -+\t\t\t\t\tnew_branch_info->commit);\n     -+\tif (!new_tree)\n     -+\t\treturn 0;\n     -+\tif (repo_parse_tree(the_repository, new_tree) < 0)\n     -+\t\treturn 0;\n     -+\n     -+\tmemset(&topts, 0, sizeof(topts));\n     -+\ttopts.head_idx = -1;\n     -+\ttopts.src_index = the_repository->index;\n     -+\ttopts.dst_index = &tmp_index;\n     -+\ttopts.initial_checkout = is_index_unborn(the_repository->index);\n     -+\ttopts.merge = 1;\n     -+\ttopts.update = 1;\n     -+\ttopts.dry_run = 1;\n     -+\ttopts.quiet = 1;\n     -+\ttopts.fn = twoway_merge;\n     -+\n     -+\tinit_tree_desc(&trees[0], &old_tree->object.oid,\n     -+\t\t       old_tree->buffer, old_tree->size);\n     -+\tinit_tree_desc(&trees[1], &new_tree->object.oid,\n     -+\t\t       new_tree->buffer, new_tree->size);\n     -+\n     -+\tret = unpack_trees(2, trees, &topts);\n     -+\tdiscard_index(&tmp_index);\n     -+\n     -+\treturn ret != 0;\n     -+}\n     -+\n     - static int switch_branches(const struct checkout_opts *opts,\n     - \t\t\t   struct branch_info *new_branch_info)\n     - {\n      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tstruct object_id rev;\n       \tint flag, writeout_error = 0;\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\tstash_label_ancestor = old_commit_shortname.buf;\n      +\t}\n      +\n     -+\tif (opts->merge) {\n     -+\t\tif (repo_read_index(the_repository) < 0)\n     -+\t\t\tdie(_(\"index file corrupt\"));\n     -+\t\tif (checkout_would_clobber_changes(&old_branch_info,\n     -+\t\t\t\t\t\t   new_branch_info)) {\n     + \tif (do_merge) {\n     + \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n     ++\t\tif (ret && opts->merge) {\n      +\t\t\tcreate_autostash_ref_silent(the_repository,\n      +\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n      +\t\t\tcreated_autostash = 1;\n     ++\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\t}\n     -+\t}\n     -+\n     - \tif (do_merge) {\n     - \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n       \t\tif (ret) {\n      +\t\t\tapply_autostash_ref_with_labels(the_repository,\n      +\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n\n-- \ngitgitgadget\n"},{"id":"539203","messageId":"cd9c64ba60ef72accafc779438db816fd0abbf27.1773740139.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v6.git.git.1773740139.gitgitgadget@gmail.com","subject":"[PATCH v6 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T09:35:36Z","receivedAt":"2026-03-17T09:35:43Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              |  2 +-\n t/t3903-stash.sh             | 18 ++++++++++++++++++\n 3 files changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex 235d57ddd8..43650a3c1a 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -197,6 +197,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--ours-label=<label>`::\n+`--theirs-label=<label>`::\n+`--base-label=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--base-label` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex e79d612e57..252e4df3a9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..dd47c1322a 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,24 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\techo base >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m base &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\techo upstream >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m upstream &&\n+\t\ttest_must_fail git stash apply --ours-label=UP --theirs-label=STASH &&\n+\t\tgrep \"^<<<<<<< UP\" file &&\n+\t\tgrep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n-- \ngitgitgadget\n\n"},{"id":"539204","messageId":"d572c4bb7df20b1dcca50f279d4a795027471407.1773740139.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v6.git.git.1773740139.gitgitgadget@gmail.com","subject":"[PATCH v6 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T09:35:37Z","receivedAt":"2026-03-17T09:35:44Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.  Use stderr for\nthe message when not silent.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex aafd0bc959..eebefd731b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4632,7 +4632,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      int silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4677,7 +4678,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4689,12 +4691,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, 0);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, 0);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex 719684c8a9..0b09d6799b 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -227,6 +227,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"539205","messageId":"80a98116fc6dc948419d731b07f5287f303c4213.1773740139.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v6.git.git.1773740139.gitgitgadget@gmail.com","subject":"[PATCH v6 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T09:35:38Z","receivedAt":"2026-03-17T09:35:45Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label1, label2, and label_ancestor parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --ours-label/--theirs-label/--base-label\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex eebefd731b..080a25820a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4704,7 +4704,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4715,6 +4717,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label1)\n+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n+\t\tif (label2)\n+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n+\t\tif (label_ancestor)\n+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4759,7 +4767,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4778,11 +4787,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4798,7 +4809,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label1, label2, label_ancestor);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4808,12 +4820,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel1, label2, label_ancestor);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 0b09d6799b..68b94d86e3 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -233,6 +233,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"539206","messageId":"7ecb0835b7680f21b1a69d33c8d33f87ce2e48b6.1773740139.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v6.git.git.1773740139.gitgitgadget@gmail.com","subject":"[PATCH v6 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T09:35:39Z","receivedAt":"2026-03-17T09:35:47Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 ++++++------\n Documentation/git-switch.adoc   |  27 +++---\n builtin/checkout.c              | 137 ++++++++++++---------------\n builtin/stash.c                 |  30 ++++--\n sequencer.c                     |  18 +++-\n t/t3420-rebase-autostash.sh     |  24 +++--\n t/t7201-co.sh                   | 160 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 10 files changed, 332 insertions(+), 137 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..18022e610e 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If the stash application results in conflicts, they are not\n+resolved and the stash is saved to the stash list:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..3147023dc3 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -126,15 +126,16 @@ variable.\n \tIf you have local modifications to one or more files that are\n \tdifferent between the current branch and the branch to which\n \tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\torder to preserve your modifications in context.  With this\n+\toption, the conflicting local changes are automatically\n+\tstashed before the switch and reapplied afterwards.  If the\n+\tlocal changes do not overlap with the differences between\n+\tbranches, the switch proceeds without stashing.  If\n+\treapplying the stash results in conflicts, the entry is\n+\tsaved to the stash list.  Resolve the conflicts and run\n+\t`git stash drop` when done, or clear the working tree\n+\t(e.g. with `git reset --hard`) before running `git stash pop`\n+\tlater to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 1d1667fa4c..dc935d6bcb 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,81 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1165,6 +1099,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_ancestor = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1202,10 +1139,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_ancestor = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_ancestor = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_ancestor);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1215,8 +1173,29 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_ancestor);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 252e4df3a9..ab9c1359b1 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label1, const char *label2,\n+\t\t\t\t      const char *label_ancestor)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label1 ? label1 : \"Updated upstream\";\n+\to.branch2 = label2 ? label2 : \"Stashed changes\";\n+\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label1, label2, label_ancestor);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/sequencer.c b/sequencer.c\nindex 080a25820a..53e04d8a94 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4741,15 +4741,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..f4fea7bb7a 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,166 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"539209","messageId":"20260317094716.43654-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqcy13o81b.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-03-17T09:47:16Z","receivedAt":"2026-03-17T09:47:20Z","isPatch":true,"body":">> Why is the dry-run of unpack_trees() not an extra check? I was assuming \n>> that it was because we do the dry-run and then do it for real after \n>> possibly stashing any local changes. That's why I was wondering if we \n>> could avoid the dry-run by creating the stash if the non-dry-run \n>> unpack_trees() failed.\n>\n> Ah, I didn't even think about that possibility.\n>\n> Try to unpack anyway, and if unpack_trees() branch switching\n> succeeds, we are done.  Otherwise, we can trust that unpack_trees()\n> did not do _anything_ to the index or the working tree files, so we\n> can create the stash at that time.\n>\n> Makes sense.\n\nInteresting idea, and thanks for your help with this! I gave it a shot with\nthis simplification.\n\nIt passes the tests, which either means it works, or just that the test\ncoverage is not good enough to detect new issues introduced by me here.\n\n\nHarald\n"},{"id":"539372","messageId":"20260319082514.49717-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"20260317094716.43654-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-03-19T08:25:14Z","receivedAt":"2026-03-19T08:25:18Z","isPatch":true,"body":"Hi Junio and Jeff!\n\nDid you get a chance to look at the latest changes?\n\nThe scope of this grew a lot from my original idea of auto-stashing, so I'm\nnot 100% convinced that changing '-m' is necessary here. My fear is to\nbreak something, especially since 'checkout -m' is a feature I never used\nbefore touching it here, so I don't have a good sense of how it should\nwork.\n\n\nHarald\n"},{"id":"539405","messageId":"xmqq4imbn4sw.fsf@gitster.g","threadId":"65220","inReplyTo":"20260319082514.49717-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-19T16:48:47Z","receivedAt":"2026-03-19T16:48:51Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> Hi Junio and Jeff!\n>\n> Did you get a chance to look at the latest changes?\n>\n> The scope of this grew a lot from my original idea of auto-stashing, so I'm\n> not 100% convinced that changing '-m' is necessary here. My fear is to\n> break something, especially since 'checkout -m' is a feature I never used\n> before touching it here, so I don't have a good sense of how it should\n> work.\n\nFWIW, I very much like what I see in \n\n   $ git checkout hn/git-checkout-m-with-stash && git diff @{1}\n\noutput.  It is great that we do not have to do any dry-run, because\nthe \"real\" run safely aborts, we can do the \"stash && merge && unstash\"\ndance as a fallback instead.  All the credit goes to Phillip and you\nfor the idea and the execution of this.\n\nI do use \"checkout -m\" a few times a week, but I do not do anything\ncomplex with submodules or run the command with unrelated local\nmodifications, so there may be changes in behaviour I haven't seen\nin corner cases that I do not exercise.\n\n\n"},{"id":"540515","messageId":"20260331121633.14907-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqq4imbn4sw.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-03-31T12:16:33Z","receivedAt":"2026-03-31T12:16:36Z","isPatch":true,"body":"> FWIW, I very much like what I see in \n> \n>    $ git checkout hn/git-checkout-m-with-stash && git diff @{1}\n> \n> output.  It is great that we do not have to do any dry-run, because\n> the \"real\" run safely aborts, we can do the \"stash && merge && unstash\"\n> dance as a fallback instead.  All the credit goes to Phillip and you\n> for the idea and the execution of this.\n> \n> I do use \"checkout -m\" a few times a week, but I do not do anything\n> complex with submodules or run the command with unrelated local\n> modifications, so there may be changes in behaviour I haven't seen\n> in corner cases that I do not exercise.\n\nI wonder if my implementation is not really up to par. I have ran into a\nfew \"conflicts\", were 'git stash pop' simply worked afterwards.\n\nSo not quite production ready.\n\n\nHarald\n"},{"id":"541254","messageId":"20260409115030.95376-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"20260331121633.14907-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T11:50:30Z","receivedAt":"2026-04-09T11:50:34Z","isPatch":true,"body":">> FWIW, I very much like what I see in \n>> \n>>    $ git checkout hn/git-checkout-m-with-stash && git diff @{1}\n>> \n>> output.  It is great that we do not have to do any dry-run, because\n>> the \"real\" run safely aborts, we can do the \"stash && merge && unstash\"\n>> dance as a fallback instead.  All the credit goes to Phillip and you\n>> for the idea and the execution of this.\n>> \n>> I do use \"checkout -m\" a few times a week, but I do not do anything\n>> complex with submodules or run the command with unrelated local\n>> modifications, so there may be changes in behaviour I haven't seen\n>> in corner cases that I do not exercise.\n>\n> I wonder if my implementation is not really up to par. I have ran into a\n> few \"conflicts\", were 'git stash pop' simply worked afterwards.\n> \n> So not quite production ready.\n\nUpdate on this: I realized that the issues I ran into was happening\nbecause of a sub-shell, so it's resolved by running like this:\n\n    export GIT_EXEC_PATH=/Users/Harald/git-repos/github.com/git/git && \\\n      /Users/Harald/git-repos/github.com/git/git/git checkout -m -\n\nSo thus, it's not a real problem.\n\nI think this is ready to be reviewed, does anyone have time to take a look?\n\n\nHarald\n"},{"id":"541256","messageId":"20260409120631.12207-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"20260331121633.14907-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T12:06:31Z","receivedAt":"2026-04-09T12:06:35Z","isPatch":true,"body":">> FWIW, I very much like what I see in \n>> \n>>    $ git checkout hn/git-checkout-m-with-stash && git diff @{1}\n>> \n>> output.  It is great that we do not have to do any dry-run, because\n>> the \"real\" run safely aborts, we can do the \"stash && merge && unstash\"\n>> dance as a fallback instead.  All the credit goes to Phillip and you\n>> for the idea and the execution of this.\n>> \n>> I do use \"checkout -m\" a few times a week, but I do not do anything\n>> complex with submodules or run the command with unrelated local\n>> modifications, so there may be changes in behaviour I haven't seen\n>> in corner cases that I do not exercise.\n>\n> I wonder if my implementation is not really up to par. I have ran into a\n> few \"conflicts\", were 'git stash pop' simply worked afterwards.\n> \n> So not quite production ready.\n\nUpdate on this: I realized that the issues I ran into was happening\nbecause of a sub-shell, so it's resolved by running like this:\n\n    export GIT_EXEC_PATH=/Users/Harald/git-repos/github.com/git/git && \\\n      /Users/Harald/git-repos/github.com/git/git/git checkout -m -\n\nSo thus, it's not a real problem.\n\nI think this is ready to be reviewed, does anyone have time to take a look?\n\n\nHarald\n"},{"id":"541257","messageId":"20260409121244.19197-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"20260331121633.14907-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T12:12:44Z","receivedAt":"2026-04-09T12:12:47Z","isPatch":true,"body":">> FWIW, I very much like what I see in \n>> \n>>    $ git checkout hn/git-checkout-m-with-stash && git diff @{1}\n>> \n>> output.  It is great that we do not have to do any dry-run, because\n>> the \"real\" run safely aborts, we can do the \"stash && merge && unstash\"\n>> dance as a fallback instead.  All the credit goes to Phillip and you\n>> for the idea and the execution of this.\n>> \n>> I do use \"checkout -m\" a few times a week, but I do not do anything\n>> complex with submodules or run the command with unrelated local\n>> modifications, so there may be changes in behaviour I haven't seen\n>> in corner cases that I do not exercise.\n>\n> I wonder if my implementation is not really up to par. I have ran into a\n> few \"conflicts\", were 'git stash pop' simply worked afterwards.\n> \n> So not quite production ready.\n\nUpdate on this: I realized that the issues I ran into was happening\nbecause of a sub-shell, so it's resolved by running like this:\n\n    export GIT_EXEC_PATH=/Users/Harald/git-repos/github.com/git/git && \\\n      /Users/Harald/git-repos/github.com/git/git/git checkout -m -\n\nSo thus, it's not a real problem.\n\nI think this is ready to be reviewed, does anyone have time to take a look?\n\n\nHarald\n"},{"id":"541264","messageId":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v6.git.git.1773740139.gitgitgadget@gmail.com","subject":"[PATCH v7 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T13:27:41Z","receivedAt":"2026-04-09T13:27:49Z","isPatch":true,"body":"Harald Nordgren (4):\n  stash: add --ours-label, --theirs-label, --base-label for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  27 ++---\n builtin/checkout.c              | 138 ++++++++++-------------\n builtin/stash.c                 |  32 ++++--\n sequencer.c                     |  67 +++++++++---\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  18 +++\n t/t7201-co.sh                   | 188 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 13 files changed, 431 insertions(+), 151 deletions(-)\n\n\nbase-commit: b15384c06f77bc2d34d0d3623a8a58218313a561\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v7\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v6:\n\n 1:  cd9c64ba60 ! 1:  284075600a stash: add --ours-label, --theirs-label, --base-label for apply\n     @@ Documentation/git-stash.adoc: git stash list [<log-options>]\n      -git stash apply [--index] [-q | --quiet] [<stash>]\n      +git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n       git stash branch <branchname> [<stash>]\n     - git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n     + git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n       \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n      @@ Documentation/git-stash.adoc: the index's ones. However, this can fail, when you have conflicts\n       (which are stored in the index, where you therefore can no longer\n 2:  d572c4bb7d = 2:  64261e3cb6 sequencer: allow create_autostash to run silently\n 3:  80a98116fc = 3:  c0d6b4b4c0 sequencer: teach autostash apply to take optional conflict marker labels\n 4:  7ecb0835b7 ! 4:  1c29e19971 checkout: -m (--merge) uses autostash when switching branches\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      -\t\t\t\t\t0, 0);\n      -\t\t\tinit_ui_merge_options(&o, the_repository);\n      -\t\t\to.verbosity = 0;\n     --\t\t\twork = write_in_core_index_as_tree(the_repository);\n     +-\t\t\twork = write_in_core_index_as_tree(the_repository,\n     +-\t\t\t\t\t\t\t   the_repository->index);\n      -\n      -\t\t\tret = reset_tree(new_tree,\n      -\t\t\t\t\t opts, 1,\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tgit reset --hard\n      +'\n      +\n     ++test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n     ++\tgit checkout -f main &&\n     ++\tgit reset --hard &&\n     ++\tgit clean -f &&\n     ++\n     ++\tgit checkout -b nonoverlap_base &&\n     ++\tfill a b c d >file &&\n     ++\tgit add file &&\n     ++\tgit commit -m \"add file\" &&\n     ++\n     ++\tgit checkout -b nonoverlap_child &&\n     ++\tfill a b c INSERTED d >file &&\n     ++\tgit commit -a -m \"insert line near end of file\" &&\n     ++\n     ++\tfill DIRTY a b c INSERTED d >file &&\n     ++\n     ++\tgit checkout -m nonoverlap_base 2>stderr &&\n     ++\ttest_grep \"Applied autostash\" stderr &&\n     ++\ttest_grep ! \"resulted in conflicts\" stderr &&\n     ++\n     ++\tfill DIRTY a b c d >expect &&\n     ++\ttest_cmp expect file &&\n     ++\n     ++\tgit checkout -f main &&\n     ++\tgit branch -D nonoverlap_base &&\n     ++\tgit branch -D nonoverlap_child\n     ++'\n     ++\n      +test_expect_success 'checkout -m -b skips stash with dirty tree' '\n      +\tgit checkout -f main &&\n      +\tgit clean -f &&\n\n-- \ngitgitgadget\n"},{"id":"541265","messageId":"284075600aeb1fc2711be72ca201de3efd81e365.1775741265.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","subject":"[PATCH v7 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T13:27:42Z","receivedAt":"2026-04-09T13:27:51Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              |  2 +-\n t/t3903-stash.sh             | 18 ++++++++++++++++++\n 3 files changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..6829ba1140 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--ours-label=<label>`::\n+`--theirs-label=<label>`::\n+`--base-label=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--base-label` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..54bcb6ac73 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..dd47c1322a 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,24 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\techo base >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m base &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\techo upstream >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m upstream &&\n+\t\ttest_must_fail git stash apply --ours-label=UP --theirs-label=STASH &&\n+\t\tgrep \"^<<<<<<< UP\" file &&\n+\t\tgrep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n-- \ngitgitgadget\n\n"},{"id":"541266","messageId":"64261e3cb6ea6e63bf78f5354e18aa94cf9b9c27.1775741265.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","subject":"[PATCH v7 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T13:27:43Z","receivedAt":"2026-04-09T13:27:52Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.  Use stderr for\nthe message when not silent.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..e500a94a59 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      int silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4702,7 +4703,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4716,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, 0);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, 0);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..570f804457 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,6 +230,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541267","messageId":"c0d6b4b4c0a53af2fd76d23e04c540418ddd086d.1775741265.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","subject":"[PATCH v7 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T13:27:44Z","receivedAt":"2026-04-09T13:27:54Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label1, label2, and label_ancestor parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --ours-label/--theirs-label/--base-label\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e500a94a59..79dceab579 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4729,7 +4729,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4740,6 +4742,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label1)\n+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n+\t\tif (label2)\n+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n+\t\tif (label_ancestor)\n+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4784,7 +4792,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4803,11 +4812,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4823,7 +4834,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label1, label2, label_ancestor);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4833,12 +4845,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel1, label2, label_ancestor);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 570f804457..1aefd25646 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -236,6 +236,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label1, const char *label2,\n+\t\t\t\t    const char *label_ancestor);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541268","messageId":"1c29e1997132273d58831c470f581fc9ae53198a.1775741265.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","subject":"[PATCH v7 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T13:27:45Z","receivedAt":"2026-04-09T13:27:56Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-switch.adoc   |  27 ++---\n builtin/checkout.c              | 138 ++++++++++-------------\n builtin/stash.c                 |  30 ++++-\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 188 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 10 files changed, 360 insertions(+), 138 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..18022e610e 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If the stash application results in conflicts, they are not\n+resolved and the stash is saved to the stash list:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..3147023dc3 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -126,15 +126,16 @@ variable.\n \tIf you have local modifications to one or more files that are\n \tdifferent between the current branch and the branch to which\n \tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\torder to preserve your modifications in context.  With this\n+\toption, the conflicting local changes are automatically\n+\tstashed before the switch and reapplied afterwards.  If the\n+\tlocal changes do not overlap with the differences between\n+\tbranches, the switch proceeds without stashing.  If\n+\treapplying the stash results in conflicts, the entry is\n+\tsaved to the stash list.  Resolve the conflicts and run\n+\t`git stash drop` when done, or clear the working tree\n+\t(e.g. with `git reset --hard`) before running `git stash pop`\n+\tlater to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..283bec2518 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1166,6 +1099,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_ancestor = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1203,10 +1139,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_ancestor = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_ancestor = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_ancestor);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1216,8 +1173,29 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_ancestor);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 54bcb6ac73..4c8e8ab59a 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label1, const char *label2,\n+\t\t\t\t      const char *label_ancestor)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label1 ? label1 : \"Updated upstream\";\n+\to.branch2 = label2 ? label2 : \"Stashed changes\";\n+\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label1, label2, label_ancestor);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/sequencer.c b/sequencer.c\nindex 79dceab579..421850f815 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4766,15 +4766,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..857a5a6bd1 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,194 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541278","messageId":"xmqqeckoul03.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 0/4] checkout: 'autostash' for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T17:00:28Z","receivedAt":"2026-04-09T17:00:31Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Harald Nordgren (4):\n>   stash: add --ours-label, --theirs-label, --base-label for apply\n>   sequencer: allow create_autostash to run silently\n>   sequencer: teach autostash apply to take optional conflict marker\n>     labels\n>   checkout: -m (--merge) uses autostash when switching branches\n\nThanks for an update.  Above the list of the commits, it would be\nhelpful to give a summary of the overall goal of the topic (which\ntypically stays more or less the same during the life of the topic)\nand the highlights of the changes since the previous iteration\n(which authors often accumulate, so that in a cover letter for v7,\nthere will be 6 such summaries), if you are sending a cover letter.\n\nI _think_ the change since v6 is a rebasing onto a more recent\n'master', and addition of a new test?\n\nThanks.\n\n>  Documentation/git-checkout.adoc |  58 +++++-----\n>  Documentation/git-stash.adoc    |  11 +-\n>  Documentation/git-switch.adoc   |  27 ++---\n>  builtin/checkout.c              | 138 ++++++++++-------------\n>  builtin/stash.c                 |  32 ++++--\n>  sequencer.c                     |  67 +++++++++---\n>  sequencer.h                     |   4 +\n>  t/t3420-rebase-autostash.sh     |  24 +++-\n>  t/t3903-stash.sh                |  18 +++\n>  t/t7201-co.sh                   | 188 ++++++++++++++++++++++++++++++++\n>  t/t7600-merge.sh                |   2 +-\n>  xdiff-interface.c               |  12 ++\n>  xdiff-interface.h               |   1 +\n>  13 files changed, 431 insertions(+), 151 deletions(-)\n>\n>\n> base-commit: b15384c06f77bc2d34d0d3623a8a58218313a561\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v7\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v7\n> Pull-Request: https://github.com/git/git/pull/2234\n>\n> Range-diff vs v6:\n>\n>  1:  cd9c64ba60 ! 1:  284075600a stash: add --ours-label, --theirs-label, --base-label for apply\n>      @@ Documentation/git-stash.adoc: git stash list [<log-options>]\n>       -git stash apply [--index] [-q | --quiet] [<stash>]\n>       +git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n>        git stash branch <branchname> [<stash>]\n>      - git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n>      + git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n>        \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n>       @@ Documentation/git-stash.adoc: the index's ones. However, this can fail, when you have conflicts\n>        (which are stored in the index, where you therefore can no longer\n>  2:  d572c4bb7d = 2:  64261e3cb6 sequencer: allow create_autostash to run silently\n>  3:  80a98116fc = 3:  c0d6b4b4c0 sequencer: teach autostash apply to take optional conflict marker labels\n>  4:  7ecb0835b7 ! 4:  1c29e19971 checkout: -m (--merge) uses autostash when switching branches\n>      @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n>       -\t\t\t\t\t0, 0);\n>       -\t\t\tinit_ui_merge_options(&o, the_repository);\n>       -\t\t\to.verbosity = 0;\n>      --\t\t\twork = write_in_core_index_as_tree(the_repository);\n>      +-\t\t\twork = write_in_core_index_as_tree(the_repository,\n>      +-\t\t\t\t\t\t\t   the_repository->index);\n>       -\n>       -\t\t\tret = reset_tree(new_tree,\n>       -\t\t\t\t\t opts, 1,\n>      @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n>       +\tgit reset --hard\n>       +'\n>       +\n>      ++test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n>      ++\tgit checkout -f main &&\n>      ++\tgit reset --hard &&\n>      ++\tgit clean -f &&\n>      ++\n>      ++\tgit checkout -b nonoverlap_base &&\n>      ++\tfill a b c d >file &&\n>      ++\tgit add file &&\n>      ++\tgit commit -m \"add file\" &&\n>      ++\n>      ++\tgit checkout -b nonoverlap_child &&\n>      ++\tfill a b c INSERTED d >file &&\n>      ++\tgit commit -a -m \"insert line near end of file\" &&\n>      ++\n>      ++\tfill DIRTY a b c INSERTED d >file &&\n>      ++\n>      ++\tgit checkout -m nonoverlap_base 2>stderr &&\n>      ++\ttest_grep \"Applied autostash\" stderr &&\n>      ++\ttest_grep ! \"resulted in conflicts\" stderr &&\n>      ++\n>      ++\tfill DIRTY a b c d >expect &&\n>      ++\ttest_cmp expect file &&\n>      ++\n>      ++\tgit checkout -f main &&\n>      ++\tgit branch -D nonoverlap_base &&\n>      ++\tgit branch -D nonoverlap_child\n>      ++'\n>      ++\n>       +test_expect_success 'checkout -m -b skips stash with dirty tree' '\n>       +\tgit checkout -f main &&\n>       +\tgit clean -f &&\n"},{"id":"541279","messageId":"xmqq7bqgujto.fsf@gitster.g","threadId":"65220","inReplyTo":"284075600aeb1fc2711be72ca201de3efd81e365.1775741265.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T17:25:55Z","receivedAt":"2026-04-09T17:25:57Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Allow callers of \"git stash apply\" to pass custom labels for conflict\n> markers instead of the default \"Updated upstream\" and \"Stashed changes\".\n> Document the new options and add a test.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>  Documentation/git-stash.adoc | 11 ++++++++++-\n>  builtin/stash.c              |  2 +-\n>  t/t3903-stash.sh             | 18 ++++++++++++++++++\n>  3 files changed, 29 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\n> index b05c990ecd..6829ba1140 100644\n> --- a/Documentation/git-stash.adoc\n> +++ b/Documentation/git-stash.adoc\n> @@ -12,7 +12,7 @@ git stash list [<log-options>]\n>  git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n>  git stash drop [-q | --quiet] [<stash>]\n>  git stash pop [--index] [-q | --quiet] [<stash>]\n> -git stash apply [--index] [-q | --quiet] [<stash>]\n> +git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n>  git stash branch <branchname> [<stash>]\n>  git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n>  \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n> @@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n>  (which are stored in the index, where you therefore can no longer\n>  apply the changes as they were originally).\n>  \n> +`--ours-label=<label>`::\n> +`--theirs-label=<label>`::\n> +`--base-label=<label>`::\n> +\tThese options are only valid for the `apply` command.\n> ++\n> +Use the given labels in conflict markers instead of the default\n> +\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n> +`--base-label` only has an effect with merge.conflictStyle=diff3.\n> +\n>  `-k`::\n>  `--keep-index`::\n>  `--no-keep-index`::\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 0d27b2fb1f..54bcb6ac73 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -44,7 +44,7 @@\n>  #define BUILTIN_STASH_POP_USAGE \\\n>  \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n>  #define BUILTIN_STASH_APPLY_USAGE \\\n> -\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n> +\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n>  #define BUILTIN_STASH_BRANCH_USAGE \\\n>  \tN_(\"git stash branch <branchname> [<stash>]\")\n>  #define BUILTIN_STASH_STORE_USAGE \\\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 70879941c2..dd47c1322a 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -1666,6 +1666,24 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'apply with custom conflict labels' '\n> +\tgit init conflict_labels &&\n> +\t(\n> +\t\tcd conflict_labels &&\n> +\t\techo base >file &&\n> +\t\tgit add file &&\n> +\t\tgit commit -m base &&\n> +\t\techo stashed >file &&\n> +\t\tgit stash push -m \"stashed\" &&\n> +\t\techo upstream >file &&\n> +\t\tgit add file &&\n> +\t\tgit commit -m upstream &&\n> +\t\ttest_must_fail git stash apply --ours-label=UP --theirs-label=STASH &&\n> +\t\tgrep \"^<<<<<<< UP\" file &&\n> +\t\tgrep \"^>>>>>>> STASH\" file\n> +\t)\n> +'\n\nTwo and a half things I noticed.\n\n * use \"test_grep\" to validate the result, like you did in other\n   patches to the tests.  t3903 is rather old and has uses of raw\n   \"grep\" but majority of the tests should already be using\n   test_grep.\n\n * Not validating the base line is a bit unexpected.  Even without\n   giving --base-label to the \"stash apply\" command, we could make\n   sure that the output says \"|||||||\" (and nothing else) for the\n   base label.\n\n * When these labels are set to an empty string, I think we should\n   refrain from adding a trailing \" \" after these marker characters.\n   Should we add a test case for that, e.g.\n\n\ttest_must_fail git stash apply --ours-l= --theirs-l= &&\n\ttest_grep \"^<<<<<<<$\" file &&\n\ttest_grep \"^>>>>>>>$\" file\n\n>  test_expect_success 'stash create reports a locked index' '\n>  \ttest_when_finished \"rm -rf repo\" &&\n>  \tgit init repo &&\n"},{"id":"541280","messageId":"xmqq1pgoujiz.fsf@gitster.g","threadId":"65220","inReplyTo":"c0d6b4b4c0a53af2fd76d23e04c540418ddd086d.1775741265.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T17:32:20Z","receivedAt":"2026-04-09T17:32:24Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Add label1, label2, and label_ancestor parameters to the autostash\n> apply machinery so callers can pass custom conflict marker labels\n> through to \"git stash apply --ours-label/--theirs-label/--base-label\".\n> Introduce apply_autostash_ref_with_labels() for callers that want\n> to pass labels.\n\nIt is just a naming thing, but the contrast between label[12] vs\nlabel_ancestor feel a bit uneven.  Wouldn't it make it easier to\ngrok a hunk like this, if you stick to ours/theirs/base terminlogy?\n\n> +\t\tif (label1)\n> +\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n> +\t\tif (label2)\n> +\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n> +\t\tif (label_ancestor)\n> +\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n\nAlternatively, if you prefer a conciseness of numbered names, it is\ncustomary to use \"1\" for the common ancestor, \"2\" for ours, and \"3\"\nfor theirs, following the same model after how higher stage index\nentries are used during a conflicting merge.\n\nOther than that, this step is quite straight-forward and looking\ngood.\n"},{"id":"541281","messageId":"xmqqjyugt3v8.fsf@gitster.g","threadId":"65220","inReplyTo":"1c29e1997132273d58831c470f581fc9ae53198a.1775741265.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v7 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T17:55:55Z","receivedAt":"2026-04-09T17:55:58Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  === 3. Merge conflict\n>  \n> -When a merge conflict happens during switching branches with\n> -the `-m` option, you would see something like this:\n> +When the `--merge` (`-m`) option is in effect and the locally\n> +modified files overlap with files that need to be updated by the\n> +branch switch, the changes are stashed and reapplied after the\n> +switch.  If the stash application results in conflicts, they are not\n> +resolved and the stash is saved to the stash list:\n\n\"results in conflicts, they are not resolved\" felt a bit funny thing\nto say.  The definition of resulting in conflicits is to be left\nwith unresolvable conflicts ;-), so ...\n\n    ... and reapplied after the switch.  If this process results in\n    conflicts, a stash entry is saved and made available in \"git\n    stash list\".  This is so that you can revisit it in case if you\n    fail to resolve conflicts correctly.\n\nor something.  But reading what you have further down, I think the\nlast sentence \"This is so that ...\" is probably unnecessary.\n\n> ...\n> +Your local changes are stashed, however, applying it to carry\n> +forward your local changes resulted in conflicts:\n>  \n> + - You can try resolving them now.  If you resolved them\n> +   successfully, discard the stash entry with \"git stash drop\".\n>  \n> + - Alternatively you can \"git reset --hard\" if you do not want\n> +   to deal with them right now, and later \"git stash pop\" to\n> +   recover your local changes.\n>  ------------\n> +\n> +You can try resolving the conflicts now.  Edit the conflicting files\n> +and mark them resolved with `git add` as usual, then run `git stash\n> +drop` to discard the stash entry.  Alternatively, you can clear the\n> +working tree with `git reset --hard` and recover your local changes\n> +later with `git stash pop`.\n\nVery well written.  I am very happy to see this text.\n\n> diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\n> index 87707e9265..3147023dc3 100644\n> --- a/Documentation/git-switch.adoc\n> +++ b/Documentation/git-switch.adoc\n> @@ -126,15 +126,16 @@ variable.\n>  \tIf you have local modifications to one or more files that are\n>  \tdifferent between the current branch and the branch to which\n>  \tyou are switching, the command refuses to switch branches in\n> +\torder to preserve your modifications in context.  With this\n> +\toption, the conflicting local changes are automatically\n> +\tstashed before the switch and reapplied afterwards.  If the\n> +\tlocal changes do not overlap with the differences between\n> +\tbranches, the switch proceeds without stashing.  If\n> +\treapplying the stash results in conflicts, the entry is\n> +\tsaved to the stash list.  Resolve the conflicts and run\n> +\t`git stash drop` when done, or clear the working tree\n> +\t(e.g. with `git reset --hard`) before running `git stash pop`\n> +\tlater to re-apply your changes.\n\nWe lost \"however\" but it is OK.\n\nWe would want to hint that \"The command refuses to\" refers to the\ndefault behaviour when option being described (\"--merge\") is not\ngiven by some other means, which was what the \"however\" we lost from\nthe updated text was trying to do.\n\n> @@ -217,15 +218,15 @@ $ git switch mytopic\n>  error: You have local changes to 'frotz'; not switching branches.\n>  ------------\n>  \n> -You can give the `-m` flag to the command, which would try a three-way\n> -merge:\n> +You can give the `-m` flag to the command, which would carry your local\n> +changes to the new branch:\n\nYes, saying \"carry your local changes to\" is much clearer than \"try\na three-way merge\".  I very much like this new text.\n\n>  ------------\n>  $ git switch -m mytopic\n> -Auto-merging frotz\n> +Switched to branch 'mytopic'\n>  ------------\n>  \n> -After this three-way merge, the local modifications are _not_\n> +After the switch, the local modifications are reapplied and are _not_\n>  registered in your index file, so `git diff` would show you what\n>  changes you made since the tip of the new branch.\n\n\nSo now to the more exciting part.  But there isn't much to comment\non, as it stayed the same since a previous iteration where Phillip's\nexcellent \"we do not have to waste cycles to dry-run at all\" idea.\n\n> diff --git a/t/t7201-co.sh b/t/t7201-co.sh\n> index 9bcf7c0b40..857a5a6bd1 100755\n> --- a/t/t7201-co.sh\n> +++ b/t/t7201-co.sh\n> @@ -210,6 +210,194 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n>  \ttest_cmp expect two\n>  '\n>  \n> +test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\tfill a b X d e >two &&\n> +\tgit checkout --merge --conflict=zdiff3 simple &&\n> +\n> +\tcat <<-EOF >expect &&\n> +\ta\n> +\t<<<<<<< simple\n> +\tc\n> +\t||||||| main\n> +\tb\n> +\tc\n> +\td\n> +\t=======\n> +\tb\n> +\tX\n> +\td\n> +\t>>>>>>> local\n> +\te\n> +\tEOF\n> +\ttest_cmp expect two\n> +'\n\nOK.  Do we want to verify with \"git stash show -p\" or something what\nwe stashed away?  I see we later have an explicit test to see that\nthe stash entry can actually be used in \"stash pop\" after clearing\nthe stage with \"checkout -f\", so perhaps it is OK to leave it to\nthat test, instead of checking in every step.\n\n> +test_expect_success 'checkout -m skips stash when no conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same\n> +'\n\nThis one may not just check \"test_grep !\" but also check the output\nof \"git stash list\", perhaps take one before and after \"checkout -m\"\nand compare them?\n\n> +test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 6 7 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"Applied autostash\" actual &&\n> +\tfill 1 2 3 4 5 6 7 >expect &&\n> +\ttest_cmp expect one\n> +'\n\nNice.\n\n> +test_expect_success 'checkout -m stashes on truly conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\ttest_must_fail git checkout side 2>stderr &&\n> +\ttest_grep \"Your local changes\" stderr &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"resulted in conflicts\" actual &&\n> +\ttest_grep \"git stash drop\" actual &&\n> +\tgit stash drop &&\n> +\tgit reset --hard\n> +'\n\nOK.  This is an emulation of a user who gives up resolving and punts.\n\n> +test_expect_success 'checkout -m produces usable stash on conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep \"recover your local changes\" actual &&\n> +\tgit checkout -f main &&\n> +\tgit stash pop &&\n> +\tfill 1 2 3 4 5 >expect &&\n> +\ttest_cmp expect one\n> +'\n\nNice.\n\n> +test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\tgit checkout -b nonoverlap_base &&\n> +\tfill a b c d >file &&\n> +\tgit add file &&\n> +\tgit commit -m \"add file\" &&\n> +\n> +\tgit checkout -b nonoverlap_child &&\n> +\tfill a b c INSERTED d >file &&\n> +\tgit commit -a -m \"insert line near end of file\" &&\n> +\n> +\tfill DIRTY a b c INSERTED d >file &&\n> +\n> +\tgit checkout -m nonoverlap_base 2>stderr &&\n> +\ttest_grep \"Applied autostash\" stderr &&\n> +\ttest_grep ! \"resulted in conflicts\" stderr &&\n> +\n> +\tfill DIRTY a b c d >expect &&\n> +\ttest_cmp expect file &&\n\nShould we or should we not see an extra stack entry saved at this point?\nDon't we want to test it?\n\n> +\n> +\tgit checkout -f main &&\n> +\tgit branch -D nonoverlap_base &&\n> +\tgit branch -D nonoverlap_child\n> +'\n\nOverall very nicely done.\n"},{"id":"541283","messageId":"xmqqbjfst21y.fsf@gitster.g","threadId":"65220","inReplyTo":"20260409120631.12207-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-09T18:35:05Z","receivedAt":"2026-04-09T18:35:08Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> Update on this: I realized that the issues I ran into was happening\n> because of a sub-shell, so it's resolved by running like this:\n>\n>     export GIT_EXEC_PATH=/Users/Harald/git-repos/github.com/git/git && \\\n>       /Users/Harald/git-repos/github.com/git/git/git checkout -m -\n>\n> So thus, it's not a real problem.\n\nIn other words, you were not consistently trying the version of Git\nyou just built?\n\nThanks for a good news.\n"},{"id":"541284","messageId":"pull.2234.v8.git.git.1775762235.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v7.git.git.1775741265.gitgitgadget@gmail.com","subject":"[PATCH v8 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T19:17:11Z","receivedAt":"2026-04-09T19:17:18Z","isPatch":true,"body":"Harald Nordgren (4):\n  stash: add --ours-label, --theirs-label, --base-label for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 +++---\n builtin/checkout.c              | 138 ++++++++++------------\n builtin/stash.c                 |  32 ++++--\n sequencer.c                     |  67 ++++++++---\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  37 ++++++\n t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 14 files changed, 463 insertions(+), 157 deletions(-)\n\n\nbase-commit: b15384c06f77bc2d34d0d3623a8a58218313a561\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v8\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v8\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v7:\n\n 1:  284075600a ! 1:  8fcf377820 stash: add --ours-label, --theirs-label, --base-label for apply\n     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n      +\t\techo upstream >file &&\n      +\t\tgit add file &&\n      +\t\tgit commit -m upstream &&\n     -+\t\ttest_must_fail git stash apply --ours-label=UP --theirs-label=STASH &&\n     -+\t\tgrep \"^<<<<<<< UP\" file &&\n     -+\t\tgrep \"^>>>>>>> STASH\" file\n     ++\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --ours-label=UP --theirs-label=STASH &&\n     ++\t\ttest_grep \"^<<<<<<< UP\" file &&\n     ++\t\ttest_grep \"^||||||| Stash base\" file &&\n     ++\t\ttest_grep \"^>>>>>>> STASH\" file\n     ++\t)\n     ++'\n     ++\n     ++test_expect_success 'apply with empty conflict labels' '\n     ++\tgit init empty_labels &&\n     ++\t(\n     ++\t\tcd empty_labels &&\n     ++\t\techo base >file &&\n     ++\t\tgit add file &&\n     ++\t\tgit commit -m base &&\n     ++\t\techo stashed >file &&\n     ++\t\tgit stash push -m \"stashed\" &&\n     ++\t\techo upstream >file &&\n     ++\t\tgit add file &&\n     ++\t\tgit commit -m upstream &&\n     ++\t\ttest_must_fail git stash apply --ours-label= --theirs-label= &&\n     ++\t\ttest_grep \"^<<<<<<<$\" file &&\n     ++\t\ttest_grep \"^>>>>>>>$\" file\n      +\t)\n      +'\n      +\n       test_expect_success 'stash create reports a locked index' '\n       \ttest_when_finished \"rm -rf repo\" &&\n       \tgit init repo &&\n     +\n     + ## xdiff/xmerge.c ##\n     +@@ xdiff/xmerge.c: static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n     + \t\t\t      int size, int i, int style,\n     + \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n     + {\n     +-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n     +-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n     +-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n     ++\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n     ++\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n     ++\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n     + \tint needs_cr = is_cr_needed(xe1, xe2, m);\n     + \n     + \tif (marker_size <= 0)\n 2:  64261e3cb6 = 2:  86cf68d024 sequencer: allow create_autostash to run silently\n 3:  c0d6b4b4c0 ! 3:  78300e0e9a sequencer: teach autostash apply to take optional conflict marker labels\n     @@ sequencer.c: void create_autostash_ref_silent(struct repository *r, const char *\n       \n      -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n      +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor)\n     ++\t\t\t\t    const char *label_ours, const char *label_theirs,\n     ++\t\t\t\t    const char *label_base)\n       {\n       \tstruct child_process child = CHILD_PROCESS_INIT;\n       \tint ret = 0;\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t\tchild.no_stderr = 1;\n       \t\tstrvec_push(&child.args, \"stash\");\n       \t\tstrvec_push(&child.args, \"apply\");\n     -+\t\tif (label1)\n     -+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label1);\n     -+\t\tif (label2)\n     -+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label2);\n     -+\t\tif (label_ancestor)\n     -+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_ancestor);\n     ++\t\tif (label_ours)\n     ++\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label_ours);\n     ++\t\tif (label_theirs)\n     ++\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label_theirs);\n     ++\t\tif (label_base)\n     ++\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_base);\n       \t\tstrvec_push(&child.args, stash_oid);\n       \t\tret = run_command(&child);\n       \t}\n     @@ sequencer.c: int apply_autostash(const char *path)\n       static int apply_save_autostash_ref(struct repository *r, const char *refname,\n      -\t\t\t\t    int attempt_apply)\n      +\t\t\t\t    int attempt_apply,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor)\n     ++\t\t\t\t    const char *label_ours, const char *label_theirs,\n     ++\t\t\t\t    const char *label_base)\n       {\n       \tstruct object_id stash_oid;\n       \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n     @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n       \toid_to_hex_r(stash_oid_hex, &stash_oid);\n      -\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n      +\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n     -+\t\t\t\t       label1, label2, label_ancestor);\n     ++\t\t\t\t       label_ours, label_theirs, label_base);\n       \n       \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n       \t\t\t&stash_oid, REF_NO_DEREF);\n     @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n      +}\n      +\n      +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor)\n     ++\t\t\t\t    const char *label_ours, const char *label_theirs,\n     ++\t\t\t\t    const char *label_base)\n      +{\n      +\treturn apply_save_autostash_ref(r, refname, 1,\n     -+\t\t\t\t\tlabel1, label2, label_ancestor);\n     ++\t\t\t\t\tlabel_ours, label_theirs, label_base);\n       }\n       \n       static int checkout_onto(struct repository *r, struct replay_opts *opts,\n     @@ sequencer.h: int save_autostash_ref(struct repository *r, const char *refname);\n       int apply_autostash_oid(const char *stash_oid);\n       int apply_autostash_ref(struct repository *r, const char *refname);\n      +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     -+\t\t\t\t    const char *label1, const char *label2,\n     -+\t\t\t\t    const char *label_ancestor);\n     ++\t\t\t\t    const char *label_ours, const char *label_theirs,\n     ++\t\t\t\t    const char *label_base);\n       \n       #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n       #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n 4:  1c29e19971 ! 4:  aa18313362 checkout: -m (--merge) uses autostash when switching branches\n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n      +When the `--merge` (`-m`) option is in effect and the locally\n      +modified files overlap with files that need to be updated by the\n      +branch switch, the changes are stashed and reapplied after the\n     -+switch.  If the stash application results in conflicts, they are not\n     -+resolved and the stash is saved to the stash list:\n     ++switch.  If this process results in conflicts, a stash entry is saved\n     ++and made available in `git stash list`:\n       \n       ------------\n       $ git checkout -m mytopic\n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n      \n       ## Documentation/git-switch.adoc ##\n      @@ Documentation/git-switch.adoc: variable.\n     - \tIf you have local modifications to one or more files that are\n     - \tdifferent between the current branch and the branch to which\n     - \tyou are switching, the command refuses to switch branches in\n     + \n     + `-m`::\n     + `--merge`::\n     +-\tIf you have local modifications to one or more files that are\n     +-\tdifferent between the current branch and the branch to which\n     +-\tyou are switching, the command refuses to switch branches in\n      -\torder to preserve your modifications in context.  However,\n      -\twith this option, a three-way merge between the current\n      -\tbranch, your working tree contents, and the new branch is\n     @@ Documentation/git-switch.adoc: variable.\n      -paths are left unmerged, and you need to resolve the conflicts\n      -and mark the resolved paths with `git add` (or `git rm` if the merge\n      -should result in deletion of the path).\n     -+\torder to preserve your modifications in context.  With this\n     -+\toption, the conflicting local changes are automatically\n     -+\tstashed before the switch and reapplied afterwards.  If the\n     -+\tlocal changes do not overlap with the differences between\n     -+\tbranches, the switch proceeds without stashing.  If\n     -+\treapplying the stash results in conflicts, the entry is\n     -+\tsaved to the stash list.  Resolve the conflicts and run\n     -+\t`git stash drop` when done, or clear the working tree\n     -+\t(e.g. with `git reset --hard`) before running `git stash pop`\n     -+\tlater to re-apply your changes.\n     ++\tIf you have local modifications to one or more files that\n     ++\tare different between the current branch and the branch to\n     ++\twhich you are switching, the command normally refuses to\n     ++\tswitch branches in order to preserve your modifications in\n     ++\tcontext.  However, with this option, the conflicting local\n     ++\tchanges are automatically stashed before the switch and\n     ++\treapplied afterwards.  If the local changes do not overlap\n     ++\twith the differences between branches, the switch proceeds\n     ++\twithout stashing.  If reapplying the stash results in\n     ++\tconflicts, the entry is saved to the stash list.  Resolve\n     ++\tthe conflicts and run `git stash drop` when done, or clear\n     ++\tthe working tree (e.g. with `git reset --hard`) before\n     ++\trunning `git stash pop` later to re-apply your changes.\n       \n       `--conflict=<style>`::\n       \tThe same as `--merge` option above, but changes the way the\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tgit clean -f &&\n      +\n      +\tfill 0 x y z >same &&\n     ++\tgit stash list >stash-before &&\n      +\tgit checkout -m side >actual 2>&1 &&\n      +\ttest_grep ! \"Created autostash\" actual &&\n     ++\tgit stash list >stash-after &&\n     ++\ttest_cmp stash-before stash-after &&\n      +\tfill 0 x y z >expect &&\n      +\ttest_cmp expect same\n      +'\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\n      +\tfill DIRTY a b c INSERTED d >file &&\n      +\n     ++\tgit stash list >stash-before &&\n      +\tgit checkout -m nonoverlap_base 2>stderr &&\n      +\ttest_grep \"Applied autostash\" stderr &&\n      +\ttest_grep ! \"resulted in conflicts\" stderr &&\n      +\n     ++\tgit stash list >stash-after &&\n     ++\ttest_cmp stash-before stash-after &&\n     ++\n      +\tfill DIRTY a b c d >expect &&\n      +\ttest_cmp expect file &&\n      +\n\n-- \ngitgitgadget\n"},{"id":"541285","messageId":"8fcf3778205d4742a56ed2e4c3b97defa21a1538.1775762235.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v8.git.git.1775762235.gitgitgadget@gmail.com","subject":"[PATCH v8 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T19:17:12Z","receivedAt":"2026-04-09T19:17:20Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              |  2 +-\n t/t3903-stash.sh             | 37 ++++++++++++++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 51 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..6829ba1140 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--ours-label=<label>`::\n+`--theirs-label=<label>`::\n+`--base-label=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--base-label` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..54bcb6ac73 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..d4e4e4d7b6 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,43 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\techo base >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m base &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\techo upstream >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m upstream &&\n+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --ours-label=UP --theirs-label=STASH &&\n+\t\ttest_grep \"^<<<<<<< UP\" file &&\n+\t\ttest_grep \"^||||||| Stash base\" file &&\n+\t\ttest_grep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit init empty_labels &&\n+\t(\n+\t\tcd empty_labels &&\n+\t\techo base >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m base &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\techo upstream >file &&\n+\t\tgit add file &&\n+\t\tgit commit -m upstream &&\n+\t\ttest_must_fail git stash apply --ours-label= --theirs-label= &&\n+\t\ttest_grep \"^<<<<<<<$\" file &&\n+\t\ttest_grep \"^>>>>>>>$\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541286","messageId":"86cf68d0240ef90ce941cc162ada658a5058795b.1775762235.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v8.git.git.1775762235.gitgitgadget@gmail.com","subject":"[PATCH v8 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T19:17:13Z","receivedAt":"2026-04-09T19:17:21Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.  Use stderr for\nthe message when not silent.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..e500a94a59 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      int silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4702,7 +4703,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4716,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, 0);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, 0);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..570f804457 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,6 +230,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541287","messageId":"78300e0e9a8beb7ffb28a38a5e203556b2df9d56.1775762235.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v8.git.git.1775762235.gitgitgadget@gmail.com","subject":"[PATCH v8 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T19:17:14Z","receivedAt":"2026-04-09T19:17:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label1, label2, and label_ancestor parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --ours-label/--theirs-label/--base-label\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e500a94a59..e28d30ff7b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4729,7 +4729,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, 1);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4740,6 +4742,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4784,7 +4792,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4803,11 +4812,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4823,7 +4834,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4833,12 +4845,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 570f804457..2c4ff17c4e 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -236,6 +236,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541288","messageId":"aa1831336297478e53ad1a2a2ab9246860181366.1775762235.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v8.git.git.1775762235.gitgitgadget@gmail.com","subject":"[PATCH v8 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-09T19:17:15Z","receivedAt":"2026-04-09T19:17:24Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-switch.adoc   |  33 +++---\n builtin/checkout.c              | 138 ++++++++++------------\n builtin/stash.c                 |  30 ++++-\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 10 files changed, 370 insertions(+), 141 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..283bec2518 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1166,6 +1099,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_ancestor = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1203,10 +1139,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_ancestor = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_ancestor = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_ancestor);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1216,8 +1173,29 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_ancestor);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 54bcb6ac73..4c8e8ab59a 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label1, const char *label2,\n+\t\t\t\t      const char *label_ancestor)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label1 ? label1 : \"Updated upstream\";\n+\to.branch2 = label2 ? label2 : \"Stashed changes\";\n+\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label1, label2, label_ancestor);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/sequencer.c b/sequencer.c\nindex e28d30ff7b..340ca4c9ce 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4766,15 +4766,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..8f02a664f4 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,201 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541300","messageId":"20260409203143.19012-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqq7bqgujto.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T20:31:43Z","receivedAt":"2026-04-09T20:31:46Z","isPatch":true,"body":"> Two and a half things I noticed.\n> \n>  * use \"test_grep\" to validate the result, like you did in other\n>    patches to the tests.  t3903 is rather old and has uses of raw\n>    \"grep\" but majority of the tests should already be using\n>    test_grep.\n> \n>  * Not validating the base line is a bit unexpected.  Even without\n>    giving --base-label to the \"stash apply\" command, we could make\n>    sure that the output says \"|||||||\" (and nothing else) for the\n>    base label.\n> \n>  * When these labels are set to an empty string, I think we should\n>    refrain from adding a trailing \" \" after these marker characters.\n>    Should we add a test case for that, e.g.\n> \n>   test_must_fail git stash apply --ours-l= --theirs-l= &&\n>   test_grep \"^<<<<<<<$\" file &&\n>   test_grep \"^>>>>>>>$\" file\n\nFixed, thanks!\n\n\nHarald\n"},{"id":"541301","messageId":"20260409203233.19082-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqjyugt3v8.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T20:32:33Z","receivedAt":"2026-04-09T20:32:39Z","isPatch":true,"body":"> Should we or should we not see an extra stack entry saved at this point?\n> Don't we want to test it?\n\nAll of these should be fixed as well. Thanks!\n\n\nHarald\n"},{"id":"541308","messageId":"20260409212024.19750-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqq1pgoujiz.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T21:20:24Z","receivedAt":"2026-04-09T21:20:27Z","isPatch":true,"body":"> It is just a naming thing, but the contrast between label[12] vs\n> label_ancestor feel a bit uneven.  Wouldn't it make it easier to\n> grok a hunk like this, if you stick to ours/theirs/base terminlogy?\n\nFixed!\n\n\nHarald\n"},{"id":"541309","messageId":"20260409212328.19875-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqeckoul03.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T21:23:28Z","receivedAt":"2026-04-09T21:23:31Z","isPatch":true,"body":"> Thanks for an update.  Above the list of the commits, it would be\n> helpful to give a summary of the overall goal of the topic (which\n> typically stays more or less the same during the life of the topic)\n> and the highlights of the changes since the previous iteration\n> (which authors often accumulate, so that in a cover letter for v7,\n> there will be 6 such summaries), if you are sending a cover letter.\n\nI'm not exactly sure how to do that with GitGitGadget.\n\nIsn't that what the commit message of the only non-preperatory commit is\nhere?\n\n\nHarald\n"},{"id":"541310","messageId":"20260409212913.20127-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqbjfst21y.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-09T21:29:13Z","receivedAt":"2026-04-09T21:29:16Z","isPatch":true,"body":"> In other words, you were not consistently trying the version of Git\nyou just built?\n\nI was, but the difference here is the this logic calls another instance of\nGit halfway through, and I didn't realize until today that that other\ninstance ended up being the system Git instead. So technically, I was only\nhalf-using it -- but accident.\n\nMaybe I should consider installing it globally on my machine, via PATH or\notherwise!\n\n\nHarald\n"},{"id":"541321","messageId":"CAPx1GvfGRx4rEEhqNbgsSKmW77o+uD2f+977cX_U_amyYdz8rg@mail.gmail.com","threadId":"65220","inReplyTo":"aa1831336297478e53ad1a2a2ab9246860181366.1775762235.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2026-04-09T23:49:49Z","receivedAt":"2026-04-09T23:50:03Z","isPatch":true,"body":"On Thu, Apr 9, 2026 at 12:18 PM Harald Nordgren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> ... If reapplying causes conflicts, the stash is\n> kept and the user is told they can resolve and run \"git stash drop\",\n> or run \"git reset --hard\" and later \"git stash pop\" to recover their\n> changes.\n\nI might suggest that this should recommend \"git stash pop --index\"\n(either always, or if the stashed index differs from the stash's parent).\n\nChris\n"},{"id":"541366","messageId":"20260410143804.25993-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"CAPx1GvfGRx4rEEhqNbgsSKmW77o+uD2f+977cX_U_amyYdz8rg@mail.gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-10T14:38:04Z","receivedAt":"2026-04-10T14:38:08Z","isPatch":true,"body":"> I might suggest that this should recommend \"git stash pop --index\"\n> (either always, or if the stashed index differs from the stash's parent).\n\nInteresting! This is a new option that I've never seen before.\n\n\n\nHarald\n"},{"id":"541379","messageId":"0d1c7bf2-6404-4779-a0d6-6db592510a04@gmail.com","threadId":"65220","inReplyTo":"8fcf3778205d4742a56ed2e4c3b97defa21a1538.1775762235.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-10T15:39:14Z","receivedAt":"2026-04-10T15:39:17Z","isPatch":true,"body":"Hi Harald\n\nOn 09/04/2026 20:17, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Allow callers of \"git stash apply\" to pass custom labels for conflict\n> markers instead of the default \"Updated upstream\" and \"Stashed changes\".\n> Document the new options and add a test.\n\nSounds sensible and the documentation looks good.\n\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 0d27b2fb1f..54bcb6ac73 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -44,7 +44,7 @@\n>   #define BUILTIN_STASH_POP_USAGE \\\n>   \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n>   #define BUILTIN_STASH_APPLY_USAGE \\\n> -\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n> +\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n>   #define BUILTIN_STASH_BRANCH_USAGE \\\n>   \tN_(\"git stash branch <branchname> [<stash>]\")\n>   #define BUILTIN_STASH_STORE_USAGE \\\n\nThis patch seems to be missing the implementation of these new options. \nBefore submitting a patch series I find it is very helpful to run\n\n     git rebase --keep-base -x make -x 'cd t && prove -j6 <tests that I \nthink might fail>'\n\nto catch any mistakes.\n\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 70879941c2..d4e4e4d7b6 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -1666,6 +1666,43 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n>   \t)\n>   '\n>   \n> +test_expect_success 'apply with custom conflict labels' '\n> +\tgit init conflict_labels &&\n\nWhy do we need to create a new repository just to stash some changes?\n\n> +\t(\n> +\t\tcd conflict_labels &&\n> +\t\techo base >file &&\n> +\t\tgit add file &&\n> +\t\tgit commit -m base &&\n\nWe have a helper test_commit() for creating commits (it is documented in \nt/test-lib-functions.sh)\n\n> +\t\techo stashed >file &&\n> +\t\tgit stash push -m \"stashed\" &&\n> +\t\techo upstream >file &&\n> +\t\tgit add file &&\n> +\t\tgit commit -m upstream &&\n> +\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --ours-label=UP --theirs-label=STASH &&\n> +\t\ttest_grep \"^<<<<<<< UP\" file &&\n> +\t\ttest_grep \"^||||||| Stash base\" file &&\n> +\t\ttest_grep \"^>>>>>>> STASH\" file\n\nHurray for the use of test_grep here!\n\n> +\t)\n> +'\n> +\n> +test_expect_success 'apply with empty conflict labels' '\n\nWhy do we want to support empty labels rather than making them an error?\n\nThanks\n\nPhillip\n\n> +\tgit init empty_labels &&\n> +\t(\n> +\t\tcd empty_labels &&\n> +\t\techo base >file &&\n> +\t\tgit add file &&\n> +\t\tgit commit -m base &&\n> +\t\techo stashed >file &&\n> +\t\tgit stash push -m \"stashed\" &&\n> +\t\techo upstream >file &&\n> +\t\tgit add file &&\n> +\t\tgit commit -m upstream &&\n> +\t\ttest_must_fail git stash apply --ours-label= --theirs-label= &&\n> +\t\ttest_grep \"^<<<<<<<$\" file &&\n> +\t\ttest_grep \"^>>>>>>>$\" file\n> +\t)\n> +'\n> +\n>   test_expect_success 'stash create reports a locked index' '\n>   \ttest_when_finished \"rm -rf repo\" &&\n>   \tgit init repo &&\n> diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\n> index 29dad98c49..659ad4ec97 100644\n> --- a/xdiff/xmerge.c\n> +++ b/xdiff/xmerge.c\n> @@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n>   \t\t\t      int size, int i, int style,\n>   \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n>   {\n> -\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n> -\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n> -\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n> +\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n> +\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n> +\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n>   \tint needs_cr = is_cr_needed(xe1, xe2, m);\n>   \n>   \tif (marker_size <= 0)\n\n"},{"id":"541380","messageId":"f8902ea9-8b18-48b2-9daa-08c7324e60f4@gmail.com","threadId":"65220","inReplyTo":"86cf68d0240ef90ce941cc162ada658a5058795b.1775762235.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 2/4] sequencer: allow create_autostash to run silently","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-10T15:39:27Z","receivedAt":"2026-04-10T15:39:29Z","isPatch":true,"body":"Hi Harald\n\nOn 09/04/2026 20:17, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Add a silent parameter to create_autostash_internal and introduce\n> create_autostash_ref_silent so that callers can create an autostash\n> without printing the \"Created autostash\" message.  Use stderr for\n> the message when not silent.\n\nWhy do we want to change where the message is printed? It is not \nnecessarily a bad idea but it would be helpful to explain why we want \nthat particular change.\n\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>   sequencer.c | 15 +++++++++++----\n>   sequencer.h |  1 +\n>   2 files changed, 12 insertions(+), 4 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index b7d8dca47f..e500a94a59 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4657,7 +4657,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n>   \n>   static void create_autostash_internal(struct repository *r,\n>   \t\t\t\t      const char *path,\n> -\t\t\t\t      const char *refname)\n> +\t\t\t\t      const char *refname,\n> +\t\t\t\t      int silent)\n\nThis could be a \"bool\" and the users could pass \"true\" and \"false\". \nApart from that this looks good.\n\nThanks\n\nPhillip\n\n>   {\n>   \tstruct strbuf buf = STRBUF_INIT;\n>   \tstruct lock_file lock_file = LOCK_INIT;\n> @@ -4702,7 +4703,8 @@ static void create_autostash_internal(struct repository *r,\n>   \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n>   \t\t}\n>   \n> -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n> +\t\tif (!silent)\n> +\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n>   \t\tif (reset_head(r, &ropts) < 0)\n>   \t\t\tdie(_(\"could not reset --hard\"));\n>   \t\tdiscard_index(r->index);\n> @@ -4714,12 +4716,17 @@ static void create_autostash_internal(struct repository *r,\n>   \n>   void create_autostash(struct repository *r, const char *path)\n>   {\n> -\tcreate_autostash_internal(r, path, NULL);\n> +\tcreate_autostash_internal(r, path, NULL, 0);\n>   }\n>   \n>   void create_autostash_ref(struct repository *r, const char *refname)\n>   {\n> -\tcreate_autostash_internal(r, NULL, refname);\n> +\tcreate_autostash_internal(r, NULL, refname, 0);\n> +}\n> +\n> +void create_autostash_ref_silent(struct repository *r, const char *refname)\n> +{\n> +\tcreate_autostash_internal(r, NULL, refname, 1);\n>   }\n>   \n>   static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n> diff --git a/sequencer.h b/sequencer.h\n> index a6fa670c7c..570f804457 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -230,6 +230,7 @@ void commit_post_rewrite(struct repository *r,\n>   \n>   void create_autostash(struct repository *r, const char *path);\n>   void create_autostash_ref(struct repository *r, const char *refname);\n> +void create_autostash_ref_silent(struct repository *r, const char *refname);\n>   int save_autostash(const char *path);\n>   int save_autostash_ref(struct repository *r, const char *refname);\n>   int apply_autostash(const char *path);\n\n"},{"id":"541381","messageId":"a93e990d-f702-4edb-958d-eab11dc5c663@gmail.com","threadId":"65220","inReplyTo":"78300e0e9a8beb7ffb28a38a5e203556b2df9d56.1775762235.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v8 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-10T15:39:34Z","receivedAt":"2026-04-10T15:39:37Z","isPatch":true,"body":"Hi Harald\n\nOn 09/04/2026 20:17, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Add label1, label2, and label_ancestor parameters to the autostash\n> apply machinery so callers can pass custom conflict marker labels\n> through to \"git stash apply --ours-label/--theirs-label/--base-label\".\n> Introduce apply_autostash_ref_with_labels() for callers that want\n> to pass labels.\n\nThis looks good. In the future we could use this to set some better \nlabels for \"git rebase --autostash\" but that does not need to be part of \nthis series.\n\nI'll leave it there for today and take a look at the last patch next \nweek. It would be a good idea to wait a few days to see if anyone else \nhas any comments before sending a new version.\n\nThanks\n\nPhillip\n\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>   sequencer.c | 34 +++++++++++++++++++++++++++-------\n>   sequencer.h |  3 +++\n>   2 files changed, 30 insertions(+), 7 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index e500a94a59..e28d30ff7b 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4729,7 +4729,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n>   \tcreate_autostash_internal(r, NULL, refname, 1);\n>   }\n>   \n> -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n> +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n> +\t\t\t\t    const char *label_ours, const char *label_theirs,\n> +\t\t\t\t    const char *label_base)\n>   {\n>   \tstruct child_process child = CHILD_PROCESS_INIT;\n>   \tint ret = 0;\n> @@ -4740,6 +4742,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n>   \t\tchild.no_stderr = 1;\n>   \t\tstrvec_push(&child.args, \"stash\");\n>   \t\tstrvec_push(&child.args, \"apply\");\n> +\t\tif (label_ours)\n> +\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label_ours);\n> +\t\tif (label_theirs)\n> +\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label_theirs);\n> +\t\tif (label_base)\n> +\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_base);\n>   \t\tstrvec_push(&child.args, stash_oid);\n>   \t\tret = run_command(&child);\n>   \t}\n> @@ -4784,7 +4792,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n>   \t}\n>   \tstrbuf_trim(&stash_oid);\n>   \n> -\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n> +\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n> +\t\t\t\t      NULL, NULL, NULL);\n>   \n>   \tunlink(path);\n>   \tstrbuf_release(&stash_oid);\n> @@ -4803,11 +4812,13 @@ int apply_autostash(const char *path)\n>   \n>   int apply_autostash_oid(const char *stash_oid)\n>   {\n> -\treturn apply_save_autostash_oid(stash_oid, 1);\n> +\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n>   }\n>   \n>   static int apply_save_autostash_ref(struct repository *r, const char *refname,\n> -\t\t\t\t    int attempt_apply)\n> +\t\t\t\t    int attempt_apply,\n> +\t\t\t\t    const char *label_ours, const char *label_theirs,\n> +\t\t\t\t    const char *label_base)\n>   {\n>   \tstruct object_id stash_oid;\n>   \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n> @@ -4823,7 +4834,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>   \t\treturn error(_(\"autostash reference is a symref\"));\n>   \n>   \toid_to_hex_r(stash_oid_hex, &stash_oid);\n> -\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n> +\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n> +\t\t\t\t       label_ours, label_theirs, label_base);\n>   \n>   \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n>   \t\t\t&stash_oid, REF_NO_DEREF);\n> @@ -4833,12 +4845,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>   \n>   int save_autostash_ref(struct repository *r, const char *refname)\n>   {\n> -\treturn apply_save_autostash_ref(r, refname, 0);\n> +\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n>   }\n>   \n>   int apply_autostash_ref(struct repository *r, const char *refname)\n>   {\n> -\treturn apply_save_autostash_ref(r, refname, 1);\n> +\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n> +}\n> +\n> +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n> +\t\t\t\t    const char *label_ours, const char *label_theirs,\n> +\t\t\t\t    const char *label_base)\n> +{\n> +\treturn apply_save_autostash_ref(r, refname, 1,\n> +\t\t\t\t\tlabel_ours, label_theirs, label_base);\n>   }\n>   \n>   static int checkout_onto(struct repository *r, struct replay_opts *opts,\n> diff --git a/sequencer.h b/sequencer.h\n> index 570f804457..2c4ff17c4e 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -236,6 +236,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n>   int apply_autostash(const char *path);\n>   int apply_autostash_oid(const char *stash_oid);\n>   int apply_autostash_ref(struct repository *r, const char *refname);\n> +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n> +\t\t\t\t    const char *label_ours, const char *label_theirs,\n> +\t\t\t\t    const char *label_base);\n>   \n>   #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n>   #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n\n"},{"id":"541386","messageId":"xmqqik9ypzax.fsf@gitster.g","threadId":"65220","inReplyTo":"0d1c7bf2-6404-4779-a0d6-6db592510a04@gmail.com","subject":"Re: [PATCH v8 1/4] stash: add --ours-label, --theirs-label, --base-label for apply","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T16:15:02Z","receivedAt":"2026-04-10T16:15:06Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> +test_expect_success 'apply with empty conflict labels' '\n>\n> Why do we want to support empty labels rather than making them an error?\n\nWhy not?\n\nThere are applications that do not require, and prefer to have more\nstable output that will not be affected by UI updates to improve\nhuman-user experience.  Even though rerere database is not populated\nwith the facility this patch implements, we can see in\n\n    $ grep -C2 -e '^\\([<=>]\\)\\1\\{6,\\}$' .git/rr-cache/*/preimage*\n\nhow having labels make the output more noisy, and more importantly,\nwill make it misleading given how rerere is designed to work,\ntreating the same merge conflicts in both directions equivalents.\nIt is not a huge stretch of imagination that our users will find\nsimilar needs, I would imagine.\n"},{"id":"541387","messageId":"xmqqeckmpz8o.fsf@gitster.g","threadId":"65220","inReplyTo":"f8902ea9-8b18-48b2-9daa-08c7324e60f4@gmail.com","subject":"Re: [PATCH v8 2/4] sequencer: allow create_autostash to run silently","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T16:16:23Z","receivedAt":"2026-04-10T16:16:25Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Add a silent parameter to create_autostash_internal and introduce\n>> create_autostash_ref_silent so that callers can create an autostash\n>> without printing the \"Created autostash\" message.  Use stderr for\n>> the message when not silent.\n>\n> Why do we want to change where the message is printed? It is not \n> necessarily a bad idea but it would be helpful to explain why we want \n> that particular change.\n\nYes, this is a great point.\n"},{"id":"541389","messageId":"xmqqa4vapyf3.fsf@gitster.g","threadId":"65220","inReplyTo":"a93e990d-f702-4edb-958d-eab11dc5c663@gmail.com","subject":"Re: [PATCH v8 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T16:34:08Z","receivedAt":"2026-04-10T16:34:11Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Harald\n>\n> On 09/04/2026 20:17, Harald Nordgren via GitGitGadget wrote:\n>> From: Harald Nordgren <haraldnordgren@gmail.com>\n>> \n>> Add label1, label2, and label_ancestor parameters to the autostash\n\nSorry, I just noticed that these three should have been updated when\nthe actual parameters were renamed.\n\n>> apply machinery so callers can pass custom conflict marker labels\n>> through to \"git stash apply --ours-label/--theirs-label/--base-label\".\n>> Introduce apply_autostash_ref_with_labels() for callers that want\n>> to pass labels.\n>\n> This looks good. In the future we could use this to set some better \n> labels for \"git rebase --autostash\" but that does not need to be part of \n> this series.\n\nGreat.\n\n"},{"id":"541395","messageId":"20260410184803.67039-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqa4vapyf3.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-10T18:48:03Z","receivedAt":"2026-04-10T18:48:06Z","isPatch":true,"body":"> Sorry, I just noticed that these three should have been updated when\n> the actual parameters were renamed.\n\nGood point!\n\nI also switched it to prefix naming label_*, which makes more sense to me.\n\n\nHarald\n"},{"id":"541396","messageId":"20260410185306.73987-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"f8902ea9-8b18-48b2-9daa-08c7324e60f4@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-10T18:53:06Z","receivedAt":"2026-04-10T18:53:09Z","isPatch":true,"body":"> Why do we want to change where the message is printed? It is not \n> necessarily a bad idea but it would be helpful to explain why we want \n> that particular change.\n\nNo good reason, and I will revert it.\n\n> This could be a \"bool\" and the users could pass \"true\" and \"false\". \n\nAgreed.\n\n\nHarald\n"},{"id":"541397","messageId":"20260410191840.38747-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"0d1c7bf2-6404-4779-a0d6-6db592510a04@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-10T19:18:40Z","receivedAt":"2026-04-10T19:18:43Z","isPatch":true,"body":"> This patch seems to be missing the implementation of these new options. \n> Before submitting a patch series I find it is very helpful to run\n> \n>      git rebase --keep-base -x make -x 'cd t && prove -j6 <tests that I \n> think might fail>'\n> \n> to catch any mistakes.\n\nWow, that command is so powerful! Thanks for sharing that!\n\nWill shift that definition to an earlier commit in my set.\n\n> Why do we need to create a new repository just to stash some changes?\n\nIsn't it good to do it in isolation, for when the test and/or its cleanup\nfails. I tried to change it now, but it's not trivial, I quickly broke a\nlot of subsequent tests.\n\n> We have a helper test_commit() for creating commits (it is documented in \n> t/test-lib-functions.sh)\n\nThanks, will update!\n\n\nHarald\n"},{"id":"541400","messageId":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v8.git.git.1775762235.gitgitgadget@gmail.com","subject":"[PATCH v9 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-10T21:01:09Z","receivedAt":"2026-04-10T21:01:17Z","isPatch":true,"body":"Harald Nordgren (4):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 +++---\n builtin/checkout.c              | 138 ++++++++++------------\n builtin/stash.c                 |  32 ++++--\n sequencer.c                     |  67 ++++++++---\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  29 +++++\n t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 14 files changed, 455 insertions(+), 157 deletions(-)\n\n\nbase-commit: cd412a49627774a14b3e49237109a77bd3ea70c0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v9\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v9\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v8:\n\n 1:  8fcf377820 ! 1:  5d5dc1f60e stash: add --ours-label, --theirs-label, --base-label for apply\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    stash: add --ours-label, --theirs-label, --base-label for apply\n     +    stash: add --label-ours, --label-theirs, --label-base for apply\n      \n          Allow callers of \"git stash apply\" to pass custom labels for conflict\n          markers instead of the default \"Updated upstream\" and \"Stashed changes\".\n     @@ Documentation/git-stash.adoc: git stash list [<log-options>]\n       git stash drop [-q | --quiet] [<stash>]\n       git stash pop [--index] [-q | --quiet] [<stash>]\n      -git stash apply [--index] [-q | --quiet] [<stash>]\n     -+git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\n     ++git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n       git stash branch <branchname> [<stash>]\n       git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n       \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n     @@ Documentation/git-stash.adoc: the index's ones. However, this can fail, when you\n       (which are stored in the index, where you therefore can no longer\n       apply the changes as they were originally).\n       \n     -+`--ours-label=<label>`::\n     -+`--theirs-label=<label>`::\n     -+`--base-label=<label>`::\n     ++`--label-ours=<label>`::\n     ++`--label-theirs=<label>`::\n     ++`--label-base=<label>`::\n      +\tThese options are only valid for the `apply` command.\n      ++\n      +Use the given labels in conflict markers instead of the default\n      +\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n     -+`--base-label` only has an effect with merge.conflictStyle=diff3.\n     ++`--label-base` only has an effect with merge.conflictStyle=diff3.\n      +\n       `-k`::\n       `--keep-index`::\n     @@ builtin/stash.c\n       \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n       #define BUILTIN_STASH_APPLY_USAGE \\\n      -\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n     -+\tN_(\"git stash apply [--index] [-q | --quiet] [--ours-label=<label>] [--theirs-label=<label>] [--base-label=<label>] [<stash>]\")\n     ++\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n       #define BUILTIN_STASH_BRANCH_USAGE \\\n       \tN_(\"git stash branch <branchname> [<stash>]\")\n       #define BUILTIN_STASH_STORE_USAGE \\\n     +@@ builtin/stash.c: static void unstage_changes_unless_new(struct object_id *orig_tree)\n     + \t\tdie(_(\"could not write index\"));\n     + }\n     + \n     +-static int do_apply_stash(const char *prefix, struct stash_info *info,\n     +-\t\t\t  int index, int quiet)\n     ++static int do_apply_stash_with_labels(const char *prefix,\n     ++\t\t\t\t      struct stash_info *info,\n     ++\t\t\t\t      int index, int quiet,\n     ++\t\t\t\t      const char *label_ours, const char *label_theirs,\n     ++\t\t\t\t      const char *label_base)\n     + {\n     + \tint clean, ret;\n     + \tint has_index = index;\n     +@@ builtin/stash.c: static int do_apply_stash(const char *prefix, struct stash_info *info,\n     + \n     + \tinit_ui_merge_options(&o, the_repository);\n     + \n     +-\to.branch1 = \"Updated upstream\";\n     +-\to.branch2 = \"Stashed changes\";\n     +-\to.ancestor = \"Stash base\";\n     ++\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n     ++\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n     ++\to.ancestor = label_base ? label_base : \"Stash base\";\n     + \n     + \tif (oideq(&info->b_tree, &c_tree))\n     + \t\to.branch1 = \"Version stash was based on\";\n     +@@ builtin/stash.c: restore_untracked:\n     + \treturn ret;\n     + }\n     + \n     ++static int do_apply_stash(const char *prefix, struct stash_info *info,\n     ++\t\t\t  int index, int quiet)\n     ++{\n     ++\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n     ++\t\t\t\t\t  NULL, NULL, NULL);\n     ++}\n     ++\n     + static int apply_stash(int argc, const char **argv, const char *prefix,\n     + \t\t       struct repository *repo UNUSED)\n     + {\n     + \tint ret = -1;\n     + \tint quiet = 0;\n     + \tint index = use_index;\n     ++\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n     + \tstruct stash_info info = STASH_INFO_INIT;\n     + \tstruct option options[] = {\n     + \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n     + \t\tOPT_BOOL(0, \"index\", &index,\n     + \t\t\t N_(\"attempt to recreate the index\")),\n     ++\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n     ++\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n     ++\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n     ++\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n     ++\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n     ++\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n     + \t\tOPT_END()\n     + \t};\n     + \n     +@@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char *prefix,\n     + \tif (get_stash_info(&info, argc, argv))\n     + \t\tgoto cleanup;\n     + \n     +-\tret = do_apply_stash(prefix, &info, index, quiet);\n     ++\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n     ++\t\t\t\t\t label_ours, label_theirs, label_base);\n     + cleanup:\n     + \tfree_stash_info(&info);\n     + \treturn ret;\n      \n       ## t/t3903-stash.sh ##\n      @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit conflicts' '\n     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n      +\tgit init conflict_labels &&\n      +\t(\n      +\t\tcd conflict_labels &&\n     -+\t\techo base >file &&\n     -+\t\tgit add file &&\n     -+\t\tgit commit -m base &&\n     ++\t\ttest_commit base file &&\n      +\t\techo stashed >file &&\n      +\t\tgit stash push -m \"stashed\" &&\n     -+\t\techo upstream >file &&\n     -+\t\tgit add file &&\n     -+\t\tgit commit -m upstream &&\n     -+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --ours-label=UP --theirs-label=STASH &&\n     ++\t\ttest_commit upstream file &&\n     ++\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n      +\t\ttest_grep \"^<<<<<<< UP\" file &&\n      +\t\ttest_grep \"^||||||| Stash base\" file &&\n      +\t\ttest_grep \"^>>>>>>> STASH\" file\n     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n      +\tgit init empty_labels &&\n      +\t(\n      +\t\tcd empty_labels &&\n     -+\t\techo base >file &&\n     -+\t\tgit add file &&\n     -+\t\tgit commit -m base &&\n     ++\t\ttest_commit base file &&\n      +\t\techo stashed >file &&\n      +\t\tgit stash push -m \"stashed\" &&\n     -+\t\techo upstream >file &&\n     -+\t\tgit add file &&\n     -+\t\tgit commit -m upstream &&\n     -+\t\ttest_must_fail git stash apply --ours-label= --theirs-label= &&\n     ++\t\ttest_commit upstream file &&\n     ++\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n      +\t\ttest_grep \"^<<<<<<<$\" file &&\n      +\t\ttest_grep \"^>>>>>>>$\" file\n      +\t)\n 2:  86cf68d024 ! 2:  a1fa04a965 sequencer: allow create_autostash to run silently\n     @@ Commit message\n      \n          Add a silent parameter to create_autostash_internal and introduce\n          create_autostash_ref_silent so that callers can create an autostash\n     -    without printing the \"Created autostash\" message.  Use stderr for\n     -    the message when not silent.\n     +    without printing the \"Created autostash\" message.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list,\n       \t\t\t\t      const char *path,\n      -\t\t\t\t      const char *refname)\n      +\t\t\t\t      const char *refname,\n     -+\t\t\t\t      int silent)\n     ++\t\t\t\t      bool silent)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n       \tstruct lock_file lock_file = LOCK_INIT;\n     @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n       \n      -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n      +\t\tif (!silent)\n     -+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n     ++\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n       \t\tif (reset_head(r, &ropts) < 0)\n       \t\t\tdie(_(\"could not reset --hard\"));\n       \t\tdiscard_index(r->index);\n     @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n       void create_autostash(struct repository *r, const char *path)\n       {\n      -\tcreate_autostash_internal(r, path, NULL);\n     -+\tcreate_autostash_internal(r, path, NULL, 0);\n     ++\tcreate_autostash_internal(r, path, NULL, false);\n       }\n       \n       void create_autostash_ref(struct repository *r, const char *refname)\n       {\n      -\tcreate_autostash_internal(r, NULL, refname);\n     -+\tcreate_autostash_internal(r, NULL, refname, 0);\n     ++\tcreate_autostash_internal(r, NULL, refname, false);\n      +}\n      +\n      +void create_autostash_ref_silent(struct repository *r, const char *refname)\n      +{\n     -+\tcreate_autostash_internal(r, NULL, refname, 1);\n     ++\tcreate_autostash_internal(r, NULL, refname, true);\n       }\n       \n       static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n 3:  78300e0e9a ! 3:  87216a633b sequencer: teach autostash apply to take optional conflict marker labels\n     @@ Metadata\n       ## Commit message ##\n          sequencer: teach autostash apply to take optional conflict marker labels\n      \n     -    Add label1, label2, and label_ancestor parameters to the autostash\n     +    Add label_ours, label_theirs, and label_base parameters to the autostash\n          apply machinery so callers can pass custom conflict marker labels\n     -    through to \"git stash apply --ours-label/--theirs-label/--base-label\".\n     +    through to \"git stash apply --label-ours/--label-theirs/--label-base\".\n          Introduce apply_autostash_ref_with_labels() for callers that want\n          to pass labels.\n      \n     @@ Commit message\n      \n       ## sequencer.c ##\n      @@ sequencer.c: void create_autostash_ref_silent(struct repository *r, const char *refname)\n     - \tcreate_autostash_internal(r, NULL, refname, 1);\n     + \tcreate_autostash_internal(r, NULL, refname, true);\n       }\n       \n      -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t\tstrvec_push(&child.args, \"stash\");\n       \t\tstrvec_push(&child.args, \"apply\");\n      +\t\tif (label_ours)\n     -+\t\t\tstrvec_pushf(&child.args, \"--ours-label=%s\", label_ours);\n     ++\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n      +\t\tif (label_theirs)\n     -+\t\t\tstrvec_pushf(&child.args, \"--theirs-label=%s\", label_theirs);\n     ++\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n      +\t\tif (label_base)\n     -+\t\t\tstrvec_pushf(&child.args, \"--base-label=%s\", label_base);\n     ++\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n       \t\tstrvec_push(&child.args, stash_oid);\n       \t\tret = run_command(&child);\n       \t}\n 4:  aa18313362 ! 4:  00e0b3196c checkout: -m (--merge) uses autostash when switching branches\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tint do_merge = 1;\n      +\tint created_autostash = 0;\n      +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n     -+\tconst char *stash_label_ancestor = NULL;\n     ++\tconst char *stash_label_base = NULL;\n       \n       \ttrace2_cmd_mode(\"branch\");\n       \n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \t}\n       \n      +\tif (old_branch_info.name)\n     -+\t\tstash_label_ancestor = old_branch_info.name;\n     ++\t\tstash_label_base = old_branch_info.name;\n      +\telse if (old_branch_info.commit) {\n      +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n      +\t\t\t\t\t &old_branch_info.commit->object.oid,\n      +\t\t\t\t\t DEFAULT_ABBREV);\n     -+\t\tstash_label_ancestor = old_commit_shortname.buf;\n     ++\t\tstash_label_base = old_commit_shortname.buf;\n      +\t}\n      +\n       \tif (do_merge) {\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n      +\t\t\t\t\t\t       new_branch_info->name,\n      +\t\t\t\t\t\t       \"local\",\n     -+\t\t\t\t\t\t       stash_label_ancestor);\n     ++\t\t\t\t\t\t       stash_label_base);\n       \t\t\tbranch_info_release(&old_branch_info);\n      +\t\t\tstrbuf_release(&old_commit_shortname);\n       \t\t\treturn ret;\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t}\n      +\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n      +\t\t\t\t       new_branch_info->name, \"local\",\n     -+\t\t\t\t       stash_label_ancestor);\n     ++\t\t\t\t       stash_label_base);\n      +\n      +\tdiscard_index(the_repository->index);\n      +\tif (repo_read_index(the_repository) < 0)\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \treturn ret || writeout_error;\n       }\n      \n     - ## builtin/stash.c ##\n     -@@ builtin/stash.c: static void unstage_changes_unless_new(struct object_id *orig_tree)\n     - \t\tdie(_(\"could not write index\"));\n     - }\n     - \n     --static int do_apply_stash(const char *prefix, struct stash_info *info,\n     --\t\t\t  int index, int quiet)\n     -+static int do_apply_stash_with_labels(const char *prefix,\n     -+\t\t\t\t      struct stash_info *info,\n     -+\t\t\t\t      int index, int quiet,\n     -+\t\t\t\t      const char *label1, const char *label2,\n     -+\t\t\t\t      const char *label_ancestor)\n     - {\n     - \tint clean, ret;\n     - \tint has_index = index;\n     -@@ builtin/stash.c: static int do_apply_stash(const char *prefix, struct stash_info *info,\n     - \n     - \tinit_ui_merge_options(&o, the_repository);\n     - \n     --\to.branch1 = \"Updated upstream\";\n     --\to.branch2 = \"Stashed changes\";\n     --\to.ancestor = \"Stash base\";\n     -+\to.branch1 = label1 ? label1 : \"Updated upstream\";\n     -+\to.branch2 = label2 ? label2 : \"Stashed changes\";\n     -+\to.ancestor = label_ancestor ? label_ancestor : \"Stash base\";\n     - \n     - \tif (oideq(&info->b_tree, &c_tree))\n     - \t\to.branch1 = \"Version stash was based on\";\n     -@@ builtin/stash.c: restore_untracked:\n     - \treturn ret;\n     - }\n     - \n     -+static int do_apply_stash(const char *prefix, struct stash_info *info,\n     -+\t\t\t  int index, int quiet)\n     -+{\n     -+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n     -+\t\t\t\t\t  NULL, NULL, NULL);\n     -+}\n     -+\n     - static int apply_stash(int argc, const char **argv, const char *prefix,\n     - \t\t       struct repository *repo UNUSED)\n     - {\n     - \tint ret = -1;\n     - \tint quiet = 0;\n     - \tint index = use_index;\n     -+\tconst char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL;\n     - \tstruct stash_info info = STASH_INFO_INIT;\n     - \tstruct option options[] = {\n     - \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n     - \t\tOPT_BOOL(0, \"index\", &index,\n     - \t\t\t N_(\"attempt to recreate the index\")),\n     -+\t\tOPT_STRING(0, \"ours-label\", &label1, N_(\"label\"),\n     -+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n     -+\t\tOPT_STRING(0, \"theirs-label\", &label2, N_(\"label\"),\n     -+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n     -+\t\tOPT_STRING(0, \"base-label\", &label_ancestor, N_(\"label\"),\n     -+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n     - \t\tOPT_END()\n     - \t};\n     - \n     -@@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char *prefix,\n     - \tif (get_stash_info(&info, argc, argv))\n     - \t\tgoto cleanup;\n     - \n     --\tret = do_apply_stash(prefix, &info, index, quiet);\n     -+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n     -+\t\t\t\t\t label1, label2, label_ancestor);\n     - cleanup:\n     - \tfree_stash_info(&info);\n     - \treturn ret;\n     -\n       ## sequencer.c ##\n      @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n       \t\tstrvec_push(&store.args, stash_oid);\n\n-- \ngitgitgadget\n"},{"id":"541401","messageId":"5d5dc1f60e92c9c10366f235a3eff554a67804b5.1775854874.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","subject":"[PATCH v9 1/4] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-10T21:01:10Z","receivedAt":"2026-04-10T21:01:19Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 32 +++++++++++++++++++++++++-------\n t/t3903-stash.sh             | 29 +++++++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 67 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..00314e2b13 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label_ours, const char *label_theirs,\n+\t\t\t\t      const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..00bcb1f802 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\t\ttest_grep \"^<<<<<<< UP\" file &&\n+\t\ttest_grep \"^||||||| Stash base\" file &&\n+\t\ttest_grep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit init empty_labels &&\n+\t(\n+\t\tcd empty_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\t\ttest_grep \"^<<<<<<<$\" file &&\n+\t\ttest_grep \"^>>>>>>>$\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541402","messageId":"87216a633b13fb2223858772cd3249ac88452ad4.1775854874.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","subject":"[PATCH v9 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-10T21:01:12Z","receivedAt":"2026-04-10T21:01:22Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, and label_base parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --label-ours/--label-theirs/--label-base\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1197d7d8a0..913be115f2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4729,7 +4729,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, true);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4740,6 +4742,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4784,7 +4792,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4803,11 +4812,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4823,7 +4834,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4833,12 +4845,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 570f804457..2c4ff17c4e 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -236,6 +236,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541403","messageId":"00e0b3196cd158244cb15bfd7edb7d9d3f0954a2.1775854874.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","subject":"[PATCH v9 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-10T21:01:13Z","receivedAt":"2026-04-10T21:01:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-switch.adoc   |  33 +++---\n builtin/checkout.c              | 138 ++++++++++------------\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 9 files changed, 346 insertions(+), 135 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..52c84bbe84 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1166,6 +1099,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1203,10 +1139,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_base = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_base);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1216,8 +1173,29 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_base);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 913be115f2..72967886d7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4766,15 +4766,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..8f02a664f4 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,201 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541404","messageId":"a1fa04a965017797e9c500bd2e6148ac4b0e4431.1775854874.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","subject":"[PATCH v9 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-10T21:01:11Z","receivedAt":"2026-04-10T21:01:34Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..1197d7d8a0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4702,7 +4703,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4716,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, false);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, false);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, true);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..570f804457 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,6 +230,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541407","messageId":"xmqqy0iumqi1.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","subject":"Re: [PATCH v9 0/4] checkout: 'autostash' for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-10T21:53:26Z","receivedAt":"2026-04-10T21:53:29Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>      -    stash: add --ours-label, --theirs-label, --base-label for apply\n>      +    stash: add --label-ours, --label-theirs, --label-base for apply\n> ...\n>      -+`--ours-label=<label>`::\n>      -+`--theirs-label=<label>`::\n>      -+`--base-label=<label>`::\n>      ++`--label-ours=<label>`::\n>      ++`--label-theirs=<label>`::\n>      ++`--label-base=<label>`::\n\nI guess it is a good change that makes things align better .  One\npotential downside is that \"--labels-o<TAB>\" is slightly longer than\n\"--ours-l<TAB>\", but I do not mind too much either way.\n\n>      @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n>        \n>       -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n>       +\t\tif (!silent)\n>      -+\t\t\tfprintf(stderr, _(\"Created autostash: %s\\n\"), buf.buf);\n>      ++\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n\nKeeping the behaviour of shared code path unchanged would be a safer\nmove, I guess.  Sending progress-like messages that are meant for\nhuman consumption to the standard error stream may be a good change\nbut should not be part of this topic, I think.\n"},{"id":"541423","messageId":"20260411183823.GA672575@coredump.intra.peff.net","threadId":"65220","inReplyTo":"00e0b3196cd158244cb15bfd7edb7d9d3f0954a2.1775854874.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v9 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-04-11T18:38:23Z","receivedAt":"2026-04-11T18:38:31Z","isPatch":true,"body":"On Fri, Apr 10, 2026 at 09:01:13PM +0000, Harald Nordgren via GitGitGadget wrote:\n\n>  \tif (do_merge) {\n>  \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\tif (ret && opts->merge) {\n> +\t\t\tcreate_autostash_ref_silent(the_repository,\n> +\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n\nThis tries to create a root-level ref called CHECKOUT_AUTOSTASH, which\nviolates the syntax rules given in gitglossary's \"ref\" entry:\n\n  Ref names must either start with refs/ or be located in the root of\n  the hierarchy. For the latter, their name must follow these rules:\n\n    •   The name consists of only upper-case characters or underscores.\n\n    •   The name ends with \"_HEAD\" or is equal to \"HEAD\".\n\nOur enforcement of these rules has some holes, but I have a local series\nto fix that (which is how I noticed the problem). The entry continues to\nlist some exceptions:\n\n  There are some irregular refs in the root of the hierarchy that do not\n  match these rules. The following list is exhaustive and shall not be\n  extended in the future:\n\n    •   AUTO_MERGE\n\n    •   BISECT_EXPECTED_REV\n\n    •   NOTES_MERGE_PARTIAL\n\n    •   NOTES_MERGE_REF\n\n    •   MERGE_AUTOSTASH\n\nWe can add CHECKOUT_AUTOSTASH to the list of exceptions, but I wonder if\nthere is another name we could use that would conform to the usual\nrules.\n\n-Peff\n"},{"id":"541425","messageId":"20260411185109.37795-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"20260411183823.GA672575@coredump.intra.peff.net","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-11T18:51:09Z","receivedAt":"2026-04-11T18:51:12Z","isPatch":true,"body":"> This tries to create a root-level ref called CHECKOUT_AUTOSTASH, which\n> violates the syntax rules given in gitglossary's \"ref\" entry:\n> \n>   Ref names must either start with refs/ or be located in the root of\n>   the hierarchy. For the latter, their name must follow these rules:\n> \n>     •   The name consists of only upper-case characters or underscores.\n> \n>     •   The name ends with \"_HEAD\" or is equal to \"HEAD\".\n\n\nSo maybe easiest is just to rename it to CHECKOUT_AUTOSTASH_HEAD?\n\n\nHarald\n"},{"id":"541427","messageId":"20260411190741.GA754996@coredump.intra.peff.net","threadId":"65220","inReplyTo":"20260411183823.GA672575@coredump.intra.peff.net","subject":"Re: [PATCH v9 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-04-11T19:07:41Z","receivedAt":"2026-04-11T19:07:43Z","isPatch":true,"body":"On Sat, Apr 11, 2026 at 02:38:23PM -0400, Jeff King wrote:\n\n> Our enforcement of these rules has some holes, but I have a local series\n> to fix that (which is how I noticed the problem). The entry continues to\n> list some exceptions:\n> \n>   There are some irregular refs in the root of the hierarchy that do not\n>   match these rules. The following list is exhaustive and shall not be\n>   extended in the future:\n> \n>     •   AUTO_MERGE\n> \n>     •   BISECT_EXPECTED_REV\n> \n>     •   NOTES_MERGE_PARTIAL\n> \n>     •   NOTES_MERGE_REF\n> \n>     •   MERGE_AUTOSTASH\n> \n> We can add CHECKOUT_AUTOSTASH to the list of exceptions, but I wonder if\n> there is another name we could use that would conform to the usual\n> rules.\n\nActually, I misread the documentation I quoted as \"list is not\nexhaustive\". ;) So we would be violating its promise to add\nCHECKOUT_AUTOSTASH to the list.\n\n-Peff\n"},{"id":"541428","messageId":"20260411191103.GA774963@coredump.intra.peff.net","threadId":"65220","inReplyTo":"20260411185109.37795-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-04-11T19:11:03Z","receivedAt":"2026-04-11T19:11:04Z","isPatch":true,"body":"On Sat, Apr 11, 2026 at 08:51:09PM +0200, Harald Nordgren wrote:\n\n> > This tries to create a root-level ref called CHECKOUT_AUTOSTASH, which\n> > violates the syntax rules given in gitglossary's \"ref\" entry:\n> > \n> >   Ref names must either start with refs/ or be located in the root of\n> >   the hierarchy. For the latter, their name must follow these rules:\n> > \n> >     •   The name consists of only upper-case characters or underscores.\n> > \n> >     •   The name ends with \"_HEAD\" or is equal to \"HEAD\".\n> \n> \n> So maybe easiest is just to rename it to CHECKOUT_AUTOSTASH_HEAD?\n\nYeah, that is syntactically valid, if a mouthful. I can't offhand think\nof a shorter variant.\n\n-Peff\n"},{"id":"541437","messageId":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v9.git.git.1775854874.gitgitgadget@gmail.com","subject":"[PATCH v10 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-12T11:51:41Z","receivedAt":"2026-04-12T11:51:48Z","isPatch":true,"body":"Harald Nordgren (4):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 +++---\n builtin/checkout.c              | 138 ++++++++++------------\n builtin/stash.c                 |  32 ++++--\n sequencer.c                     |  67 ++++++++---\n sequencer.h                     |   4 +\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  29 +++++\n t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 14 files changed, 455 insertions(+), 157 deletions(-)\n\n\nbase-commit: 8c9303b1ffae5b745d1b0a1f98330cf7944d8db0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v10\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v10\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v9:\n\n 1:  5d5dc1f60e = 1:  aa519c50be stash: add --label-ours, --label-theirs, --label-base for apply\n 2:  a1fa04a965 = 2:  8173a4eb72 sequencer: allow create_autostash to run silently\n 3:  87216a633b = 3:  f36fedd8c2 sequencer: teach autostash apply to take optional conflict marker labels\n 4:  00e0b3196c ! 4:  b6e5546d74 checkout: -m (--merge) uses autostash when switching branches\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\tif (ret && opts->merge) {\n      +\t\t\tcreate_autostash_ref_silent(the_repository,\n     -+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n     ++\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\");\n      +\t\t\tcreated_autostash = 1;\n      +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\t}\n       \t\tif (ret) {\n      +\t\t\tapply_autostash_ref_with_labels(the_repository,\n     -+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH\",\n     ++\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n      +\t\t\t\t\t\t       new_branch_info->name,\n      +\t\t\t\t\t\t       \"local\",\n      +\t\t\t\t\t\t       stash_label_base);\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\tgit_config_push_parameter(cfg.buf);\n      +\t\tstrbuf_release(&cfg);\n      +\t}\n     -+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH\",\n     ++\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n      +\t\t\t\t       new_branch_info->name, \"local\",\n      +\t\t\t\t       stash_label_base);\n      +\n\n-- \ngitgitgadget\n"},{"id":"541438","messageId":"aa519c50be74b36a188f761d82e11e3ebf84adf9.1775994705.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"[PATCH v10 1/4] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-12T11:51:42Z","receivedAt":"2026-04-12T11:51:49Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 32 +++++++++++++++++++++++++-------\n t/t3903-stash.sh             | 29 +++++++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 67 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..00314e2b13 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label_ours, const char *label_theirs,\n+\t\t\t\t      const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..00bcb1f802 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\t\ttest_grep \"^<<<<<<< UP\" file &&\n+\t\ttest_grep \"^||||||| Stash base\" file &&\n+\t\ttest_grep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit init empty_labels &&\n+\t(\n+\t\tcd empty_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\t\ttest_grep \"^<<<<<<<$\" file &&\n+\t\ttest_grep \"^>>>>>>>$\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541439","messageId":"8173a4eb724646e66aaf6cd8d78b1560e18c14a2.1775994705.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"[PATCH v10 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-12T11:51:43Z","receivedAt":"2026-04-12T11:51:50Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..1197d7d8a0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4702,7 +4703,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4716,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, false);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, false);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, true);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..570f804457 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,6 +230,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541440","messageId":"f36fedd8c298159f3fee68c74e6640c18490703f.1775994705.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"[PATCH v10 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-12T11:51:44Z","receivedAt":"2026-04-12T11:51:52Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, and label_base parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --label-ours/--label-theirs/--label-base\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1197d7d8a0..913be115f2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4729,7 +4729,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, true);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4740,6 +4742,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4784,7 +4792,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4803,11 +4812,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4823,7 +4834,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4833,12 +4845,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 570f804457..2c4ff17c4e 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -236,6 +236,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541441","messageId":"b6e5546d74b2ebbd0a5307db76e9d6e0c41ed781.1775994705.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"[PATCH v10 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-12T11:51:45Z","receivedAt":"2026-04-12T11:51:54Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 +++++-----\n Documentation/git-switch.adoc   |  33 +++---\n builtin/checkout.c              | 138 ++++++++++------------\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 195 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 9 files changed, 346 insertions(+), 135 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..47391d453f 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1166,6 +1099,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1203,10 +1139,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_base = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tcreate_autostash_ref_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\");\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_base);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1216,8 +1173,29 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_base);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 913be115f2..72967886d7 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4766,15 +4766,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..8f02a664f4 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,201 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541447","messageId":"20260412200107.GB1691477@coredump.intra.peff.net","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 0/4] checkout: 'autostash' for branch switching","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-04-12T20:01:07Z","receivedAt":"2026-04-12T20:01:09Z","isPatch":true,"body":"On Sun, Apr 12, 2026 at 11:51:41AM +0000, Harald Nordgren via GitGitGadget wrote:\n\n>  4:  00e0b3196c ! 4:  b6e5546d74 checkout: -m (--merge) uses autostash when switching branches\n>      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>        \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>       +\t\tif (ret && opts->merge) {\n>       +\t\t\tcreate_autostash_ref_silent(the_repository,\n>      -+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH\");\n>      ++\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\");\n> [...]\n\nThanks, I can confirm that this version is fine with the stricter\nref-syntax checks.\n\n-Peff\n"},{"id":"541504","messageId":"xmqqa4v6fpj6.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"Re: [PATCH v10 0/4] checkout: 'autostash' for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-13T22:45:17Z","receivedAt":"2026-04-13T22:45:21Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Harald Nordgren (4):\n>   stash: add --label-ours, --label-theirs, --label-base for apply\n>   sequencer: allow create_autostash to run silently\n>   sequencer: teach autostash apply to take optional conflict marker\n>     labels\n>   checkout: -m (--merge) uses autostash when switching branches\n\nI have been trying this in my real workflow and noticed only one\nminor annoyance, which is that the stash entry only says\n\"autostash\", which is not very illuminating.\n\nBecause I almost always have either 'master' or 'next' checked out,\nwhen I start outlining a \"how about this\" kind of change, they are\nmade on top of these branches, but when I say \"checkout -m topic\"\nafter that, I _know_ that the rough draft change that becomes a\nstash entry is meant to be part of the \"topic\", either to extend it\nor refine it.  Because the code that creates the stash entry knows\nthat we were in the process of moving to 'topic', it would be nice\nto see the name of the branch we are moving to (i.e., 'topic') on\nthe title, e.g., \"autostash while switching to 'topic'\".\n\nOther than that, it is a very pleasant usability enhancement.\n\nThanks.\n\n\n\n"},{"id":"541530","messageId":"20260414072950.61240-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqa4v6fpj6.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T07:29:50Z","receivedAt":"2026-04-14T07:29:54Z","isPatch":true,"body":"> Because I almost always have either 'master' or 'next' checked out,\n> when I start outlining a \"how about this\" kind of change, they are\n> made on top of these branches, but when I say \"checkout -m topic\"\n> after that, I _know_ that the rough draft change that becomes a\n> stash entry is meant to be part of the \"topic\", either to extend it\n> or refine it.  Because the code that creates the stash entry knows\n> that we were in the process of moving to 'topic', it would be nice\n> to see the name of the branch we are moving to (i.e., 'topic') on\n> the title, e.g., \"autostash while switching to 'topic'\".\n\nSounds reasonable, but wouldn't it make more sense to call it \"autostash\nfrom master\". We should still be able to abort the merge and merge it to\nsome other branch. I feel like the source is more relevant than the\ndestination, no?\n\n\nHarald\n"},{"id":"541541","messageId":"pull.2234.v11.git.git.1776163819.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v10.git.git.1775994705.gitgitgadget@gmail.com","subject":"[PATCH v11 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T10:50:15Z","receivedAt":"2026-04-14T10:50:22Z","isPatch":true,"body":"Harald Nordgren (4):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 147 ++++++++++------------\n builtin/stash.c                 |  32 +++--\n sequencer.c                     |  87 ++++++++++---\n sequencer.h                     |   8 ++\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  29 +++++\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 14 files changed, 499 insertions(+), 159 deletions(-)\n\n\nbase-commit: 9e8f4e9c04e3efa494e78b710e0c5f6cc77a0a5e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v11\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v11\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v10:\n\n 1:  aa519c50be = 1:  9ab5431b47 stash: add --label-ours, --label-theirs, --label-base for apply\n 2:  8173a4eb72 = 2:  e7f8328e3c sequencer: allow create_autostash to run silently\n 3:  f36fedd8c2 = 3:  3242fd3261 sequencer: teach autostash apply to take optional conflict marker labels\n 4:  b6e5546d74 ! 4:  97a5d87c81 checkout: -m (--merge) uses autostash when switching branches\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tint do_merge = 1;\n      +\tint created_autostash = 0;\n      +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n     ++\tstruct strbuf autostash_msg = STRBUF_INIT;\n      +\tconst char *stash_label_base = NULL;\n       \n       \ttrace2_cmd_mode(\"branch\");\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tif (do_merge) {\n       \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\tif (ret && opts->merge) {\n     -+\t\t\tcreate_autostash_ref_silent(the_repository,\n     -+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\");\n     ++\t\t\tstrbuf_addf(&autostash_msg,\n     ++\t\t\t\t    \"autostash while switching to '%s'\",\n     ++\t\t\t\t    new_branch_info->name);\n     ++\t\t\tcreate_autostash_ref_silent_with_msg(the_repository,\n     ++\t\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n     ++\t\t\t\t\t\t\t    autostash_msg.buf);\n      +\t\t\tcreated_autostash = 1;\n      +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\t}\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n      +\t\t\t\t\t\t       new_branch_info->name,\n      +\t\t\t\t\t\t       \"local\",\n     -+\t\t\t\t\t\t       stash_label_base);\n     ++\t\t\t\t\t\t       stash_label_base,\n     ++\t\t\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n       \t\t\tbranch_info_release(&old_branch_info);\n      +\t\t\tstrbuf_release(&old_commit_shortname);\n     ++\t\t\tstrbuf_release(&autostash_msg);\n       \t\t\treturn ret;\n       \t\t}\n       \t}\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t}\n      +\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n      +\t\t\t\t       new_branch_info->name, \"local\",\n     -+\t\t\t\t       stash_label_base);\n     ++\t\t\t\t       stash_label_base,\n     ++\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n      +\n      +\tdiscard_index(the_repository->index);\n      +\tif (repo_read_index(the_repository) < 0)\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n       \tbranch_info_release(&old_branch_info);\n      +\tstrbuf_release(&old_commit_shortname);\n     ++\tstrbuf_release(&autostash_msg);\n       \n       \treturn ret || writeout_error;\n       }\n      \n       ## sequencer.c ##\n     +@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n     + static void create_autostash_internal(struct repository *r,\n     + \t\t\t\t      const char *path,\n     + \t\t\t\t      const char *refname,\n     ++\t\t\t\t      const char *message,\n     + \t\t\t\t      bool silent)\n     + {\n     + \tstruct strbuf buf = STRBUF_INIT;\n     +@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     + \t\tstruct object_id oid;\n     + \n     + \t\tstrvec_pushl(&stash.args,\n     +-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n     ++\t\t\t     \"stash\", \"create\",\n     ++\t\t\t     message ? message : \"autostash\", NULL);\n     + \t\tstash.git_cmd = 1;\n     + \t\tstash.no_stdin = 1;\n     + \t\tstrbuf_reset(&buf);\n     +@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     + \n     + void create_autostash(struct repository *r, const char *path)\n     + {\n     +-\tcreate_autostash_internal(r, path, NULL, false);\n     ++\tcreate_autostash_internal(r, path, NULL, NULL, false);\n     + }\n     + \n     + void create_autostash_ref(struct repository *r, const char *refname)\n     + {\n     +-\tcreate_autostash_internal(r, NULL, refname, false);\n     ++\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n     + }\n     + \n     + void create_autostash_ref_silent(struct repository *r, const char *refname)\n     + {\n     +-\tcreate_autostash_internal(r, NULL, refname, true);\n     ++\tcreate_autostash_internal(r, NULL, refname, NULL, true);\n     ++}\n     ++\n     ++void create_autostash_ref_silent_with_msg(struct repository *r,\n     ++\t\t\t\t\t  const char *refname,\n     ++\t\t\t\t\t  const char *message)\n     ++{\n     ++\tcreate_autostash_internal(r, NULL, refname, message, true);\n     + }\n     + \n     + static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     + \t\t\t\t    const char *label_ours, const char *label_theirs,\n     +-\t\t\t\t    const char *label_base)\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg)\n     + {\n     + \tstruct child_process child = CHILD_PROCESS_INIT;\n     + \tint ret = 0;\n      @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     + \t\tstrvec_push(&store.args, \"stash\");\n     + \t\tstrvec_push(&store.args, \"store\");\n     + \t\tstrvec_push(&store.args, \"-m\");\n     +-\t\tstrvec_push(&store.args, \"autostash\");\n     ++\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n     + \t\tstrvec_push(&store.args, \"-q\");\n       \t\tstrvec_push(&store.args, stash_oid);\n       \t\tif (run_command(&store))\n       \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t}\n       \n       \treturn ret;\n     +@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n     + \tstrbuf_trim(&stash_oid);\n     + \n     + \tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n     +-\t\t\t\t      NULL, NULL, NULL);\n     ++\t\t\t\t      NULL, NULL, NULL, NULL);\n     + \n     + \tunlink(path);\n     + \tstrbuf_release(&stash_oid);\n     +@@ sequencer.c: int apply_autostash(const char *path)\n     + \n     + int apply_autostash_oid(const char *stash_oid)\n     + {\n     +-\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n     ++\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n     + }\n     + \n     + static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     + \t\t\t\t    int attempt_apply,\n     + \t\t\t\t    const char *label_ours, const char *label_theirs,\n     +-\t\t\t\t    const char *label_base)\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg)\n     + {\n     + \tstruct object_id stash_oid;\n     + \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n     +@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     + \n     + \toid_to_hex_r(stash_oid_hex, &stash_oid);\n     + \tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n     +-\t\t\t\t       label_ours, label_theirs, label_base);\n     ++\t\t\t\t       label_ours, label_theirs, label_base,\n     ++\t\t\t\t       stash_msg);\n     + \n     + \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n     + \t\t\t&stash_oid, REF_NO_DEREF);\n     +@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     + \n     + int save_autostash_ref(struct repository *r, const char *refname)\n     + {\n     +-\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n     ++\treturn apply_save_autostash_ref(r, refname, 0,\n     ++\t\t\t\t\tNULL, NULL, NULL, NULL);\n     + }\n     + \n     + int apply_autostash_ref(struct repository *r, const char *refname)\n     + {\n     +-\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n     ++\treturn apply_save_autostash_ref(r, refname, 1,\n     ++\t\t\t\t\tNULL, NULL, NULL, NULL);\n     + }\n     + \n     + int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     + \t\t\t\t    const char *label_ours, const char *label_theirs,\n     +-\t\t\t\t    const char *label_base)\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg)\n     + {\n     + \treturn apply_save_autostash_ref(r, refname, 1,\n     +-\t\t\t\t\tlabel_ours, label_theirs, label_base);\n     ++\t\t\t\t\tlabel_ours, label_theirs, label_base,\n     ++\t\t\t\t\tstash_msg);\n     + }\n     + \n     + static int checkout_onto(struct repository *r, struct replay_opts *opts,\n     +\n     + ## sequencer.h ##\n     +@@ sequencer.h: void commit_post_rewrite(struct repository *r,\n     + void create_autostash(struct repository *r, const char *path);\n     + void create_autostash_ref(struct repository *r, const char *refname);\n     + void create_autostash_ref_silent(struct repository *r, const char *refname);\n     ++void create_autostash_ref_silent_with_msg(struct repository *r,\n     ++\t\t\t\t\t  const char *refname,\n     ++\t\t\t\t\t  const char *message);\n     + int save_autostash(const char *path);\n     + int save_autostash_ref(struct repository *r, const char *refname);\n     + int apply_autostash(const char *path);\n     +@@ sequencer.h: int apply_autostash_oid(const char *stash_oid);\n     + int apply_autostash_ref(struct repository *r, const char *refname);\n     + int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     + \t\t\t\t    const char *label_ours, const char *label_theirs,\n     +-\t\t\t\t    const char *label_base);\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg);\n     + \n     + #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n     + #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n      \n       ## t/t3420-rebase-autostash.sh ##\n      @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\ttest_cmp expect one\n      +'\n      +\n     ++test_expect_success 'checkout -m autostash message includes target branch' '\n     ++\tgit checkout -f main &&\n     ++\tgit clean -f &&\n     ++\n     ++\tfill 1 2 3 4 5 >one &&\n     ++\tgit checkout -m side >actual 2>&1 &&\n     ++\tgit stash list >stash-list &&\n     ++\ttest_grep \"autostash while switching to .side.\" stash-list &&\n     ++\tgit stash drop &&\n     ++\tgit checkout -f main &&\n     ++\tgit reset --hard\n     ++'\n     ++\n      +test_expect_success 'checkout -m stashes on staged conflicting changes' '\n      +\tgit checkout -f main &&\n      +\tgit clean -f &&\n\n-- \ngitgitgadget\n"},{"id":"541542","messageId":"9ab5431b4773c29097ae9bdd497822477c7ba56a.1776163819.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v11.git.git.1776163819.gitgitgadget@gmail.com","subject":"[PATCH v11 1/4] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T10:50:16Z","receivedAt":"2026-04-14T10:50:24Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 32 +++++++++++++++++++++++++-------\n t/t3903-stash.sh             | 29 +++++++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 67 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..00314e2b13 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label_ours, const char *label_theirs,\n+\t\t\t\t      const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..00bcb1f802 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\t\ttest_grep \"^<<<<<<< UP\" file &&\n+\t\ttest_grep \"^||||||| Stash base\" file &&\n+\t\ttest_grep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit init empty_labels &&\n+\t(\n+\t\tcd empty_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\t\ttest_grep \"^<<<<<<<$\" file &&\n+\t\ttest_grep \"^>>>>>>>$\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541543","messageId":"e7f8328e3c2c4ec746f095ab35b5f2d310878a1e.1776163819.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v11.git.git.1776163819.gitgitgadget@gmail.com","subject":"[PATCH v11 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T10:50:17Z","receivedAt":"2026-04-14T10:50:26Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 15 +++++++++++----\n sequencer.h |  1 +\n 2 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..1197d7d8a0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,8 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4702,7 +4703,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4716,17 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, false);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, false);\n+}\n+\n+void create_autostash_ref_silent(struct repository *r, const char *refname)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, true);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..570f804457 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,6 +230,7 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_silent(struct repository *r, const char *refname);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541544","messageId":"3242fd32613426cecc248cd19cd0e5cd176e87d2.1776163819.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v11.git.git.1776163819.gitgitgadget@gmail.com","subject":"[PATCH v11 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T10:50:18Z","receivedAt":"2026-04-14T10:50:27Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, and label_base parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --label-ours/--label-theirs/--label-base\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 34 +++++++++++++++++++++++++++-------\n sequencer.h |  3 +++\n 2 files changed, 30 insertions(+), 7 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1197d7d8a0..913be115f2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4729,7 +4729,9 @@ void create_autostash_ref_silent(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, true);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4740,6 +4742,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4784,7 +4792,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4803,11 +4812,13 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4823,7 +4834,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4833,12 +4845,20 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 570f804457..2c4ff17c4e 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -236,6 +236,9 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541545","messageId":"97a5d87c811bddc3db2f74546b2ec9d030fafb72.1776163819.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v11.git.git.1776163819.gitgitgadget@gmail.com","subject":"[PATCH v11 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T10:50:19Z","receivedAt":"2026-04-14T10:50:29Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 147 ++++++++++------------\n sequencer.c                     |  62 +++++++---\n sequencer.h                     |   6 +-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 10 files changed, 403 insertions(+), 150 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..2adfefa085 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1166,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tstruct strbuf autostash_msg = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1203,10 +1140,37 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_base = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tstrbuf_addf(&autostash_msg,\n+\t\t\t\t    \"autostash while switching to '%s'\",\n+\t\t\t\t    new_branch_info->name);\n+\t\t\tcreate_autostash_ref_silent_with_msg(the_repository,\n+\t\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t\t    autostash_msg.buf);\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_base,\n+\t\t\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n+\t\t\tstrbuf_release(&autostash_msg);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1216,8 +1180,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_base,\n+\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n+\tstrbuf_release(&autostash_msg);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 913be115f2..febdb5b20a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4658,6 +4658,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n \t\t\t\t      const char *refname,\n+\t\t\t\t      const char *message,\n \t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -4680,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n+\t\t\t     \"stash\", \"create\",\n+\t\t\t     message ? message : \"autostash\", NULL);\n \t\tstash.git_cmd = 1;\n \t\tstash.no_stdin = 1;\n \t\tstrbuf_reset(&buf);\n@@ -4716,22 +4718,30 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL, false);\n+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname, false);\n+\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n }\n \n void create_autostash_ref_silent(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname, true);\n+\tcreate_autostash_internal(r, NULL, refname, NULL, true);\n+}\n+\n+void create_autostash_ref_silent_with_msg(struct repository *r,\n+\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t  const char *message)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, message, true);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\t\t\t    const char *label_ours, const char *label_theirs,\n-\t\t\t\t    const char *label_base)\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4761,20 +4771,28 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, \"stash\");\n \t\tstrvec_push(&store.args, \"store\");\n \t\tstrvec_push(&store.args, \"-m\");\n-\t\tstrvec_push(&store.args, \"autostash\");\n+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n \t\tstrvec_push(&store.args, \"-q\");\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\n@@ -4793,7 +4811,7 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \tstrbuf_trim(&stash_oid);\n \n \tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n-\t\t\t\t      NULL, NULL, NULL);\n+\t\t\t\t      NULL, NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4812,13 +4830,14 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\t\t\t    int attempt_apply,\n \t\t\t\t    const char *label_ours, const char *label_theirs,\n-\t\t\t\t    const char *label_base)\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4835,7 +4854,8 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n \tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n-\t\t\t\t       label_ours, label_theirs, label_base);\n+\t\t\t\t       label_ours, label_theirs, label_base,\n+\t\t\t\t       stash_msg);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4845,20 +4865,24 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n+\treturn apply_save_autostash_ref(r, refname, 0,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n \t\t\t\t    const char *label_ours, const char *label_theirs,\n-\t\t\t\t    const char *label_base)\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \treturn apply_save_autostash_ref(r, refname, 1,\n-\t\t\t\t\tlabel_ours, label_theirs, label_base);\n+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n+\t\t\t\t\tstash_msg);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 2c4ff17c4e..67de755053 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -231,6 +231,9 @@ void commit_post_rewrite(struct repository *r,\n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n void create_autostash_ref_silent(struct repository *r, const char *refname);\n+void create_autostash_ref_silent_with_msg(struct repository *r,\n+\t\t\t\t\t  const char *refname,\n+\t\t\t\t\t  const char *message);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n@@ -238,7 +241,8 @@ int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n \t\t\t\t    const char *label_ours, const char *label_theirs,\n-\t\t\t\t    const char *label_base);\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..c474c6759f 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m autostash message includes target branch' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\tgit stash list >stash-list &&\n+\ttest_grep \"autostash while switching to .side.\" stash-list &&\n+\tgit stash drop &&\n+\tgit checkout -f main &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541546","messageId":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v11.git.git.1776163819.gitgitgadget@gmail.com","subject":"[PATCH v12 0/4] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T12:59:41Z","receivedAt":"2026-04-14T12:59:49Z","isPatch":true,"body":"Harald Nordgren (4):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: -m (--merge) uses autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 147 ++++++++++------------\n builtin/stash.c                 |  32 +++--\n sequencer.c                     |  81 ++++++++++---\n sequencer.h                     |   6 +\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  29 +++++\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 14 files changed, 491 insertions(+), 159 deletions(-)\n\n\nbase-commit: 9e8f4e9c04e3efa494e78b710e0c5f6cc77a0a5e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v12\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v12\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v11:\n\n 1:  9ab5431b47 = 1:  9ab5431b47 stash: add --label-ours, --label-theirs, --label-base for apply\n 2:  e7f8328e3c ! 2:  e11a622bdf sequencer: allow create_autostash to run silently\n     @@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list,\n       \t\t\t\t      const char *path,\n      -\t\t\t\t      const char *refname)\n      +\t\t\t\t      const char *refname,\n     ++\t\t\t\t      const char *message,\n      +\t\t\t\t      bool silent)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n       \tstruct lock_file lock_file = LOCK_INIT;\n     +@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     + \t\tstruct object_id oid;\n     + \n     + \t\tstrvec_pushl(&stash.args,\n     +-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n     ++\t\t\t     \"stash\", \"create\",\n     ++\t\t\t     message ? message : \"autostash\", NULL);\n     + \t\tstash.git_cmd = 1;\n     + \t\tstash.no_stdin = 1;\n     + \t\tstrbuf_reset(&buf);\n      @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n       \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n       \t\t}\n     @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n       void create_autostash(struct repository *r, const char *path)\n       {\n      -\tcreate_autostash_internal(r, path, NULL);\n     -+\tcreate_autostash_internal(r, path, NULL, false);\n     ++\tcreate_autostash_internal(r, path, NULL, NULL, false);\n       }\n       \n       void create_autostash_ref(struct repository *r, const char *refname)\n       {\n      -\tcreate_autostash_internal(r, NULL, refname);\n     -+\tcreate_autostash_internal(r, NULL, refname, false);\n     ++\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n      +}\n      +\n     -+void create_autostash_ref_silent(struct repository *r, const char *refname)\n     ++void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n     ++\t\t\t\t const char *message)\n      +{\n     -+\tcreate_autostash_internal(r, NULL, refname, true);\n     ++\tcreate_autostash_internal(r, NULL, refname, message, true);\n       }\n       \n       static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     @@ sequencer.h: void commit_post_rewrite(struct repository *r,\n       \n       void create_autostash(struct repository *r, const char *path);\n       void create_autostash_ref(struct repository *r, const char *refname);\n     -+void create_autostash_ref_silent(struct repository *r, const char *refname);\n     ++void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n     ++\t\t\t\t const char *message);\n       int save_autostash(const char *path);\n       int save_autostash_ref(struct repository *r, const char *refname);\n       int apply_autostash(const char *path);\n 3:  3242fd3261 ! 3:  4593745e90 sequencer: teach autostash apply to take optional conflict marker labels\n     @@ Commit message\n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## sequencer.c ##\n     -@@ sequencer.c: void create_autostash_ref_silent(struct repository *r, const char *refname)\n     - \tcreate_autostash_internal(r, NULL, refname, true);\n     +@@ sequencer.c: void create_autostash_ref_with_msg_silent(struct repository *r, const char *refn\n     + \tcreate_autostash_internal(r, NULL, refname, message, true);\n       }\n       \n      -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n      +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n      +\t\t\t\t    const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t    const char *label_base)\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg)\n       {\n       \tstruct child_process child = CHILD_PROCESS_INIT;\n       \tint ret = 0;\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t\tstrvec_push(&child.args, stash_oid);\n       \t\tret = run_command(&child);\n       \t}\n     +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     + \t\tstrvec_push(&store.args, \"stash\");\n     + \t\tstrvec_push(&store.args, \"store\");\n     + \t\tstrvec_push(&store.args, \"-m\");\n     +-\t\tstrvec_push(&store.args, \"autostash\");\n     ++\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n     + \t\tstrvec_push(&store.args, \"-q\");\n     + \t\tstrvec_push(&store.args, stash_oid);\n     + \t\tif (run_command(&store))\n      @@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n       \t}\n       \tstrbuf_trim(&stash_oid);\n       \n      -\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n      +\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n     -+\t\t\t\t      NULL, NULL, NULL);\n     ++\t\t\t\t      NULL, NULL, NULL, NULL);\n       \n       \tunlink(path);\n       \tstrbuf_release(&stash_oid);\n     @@ sequencer.c: int apply_autostash(const char *path)\n       int apply_autostash_oid(const char *stash_oid)\n       {\n      -\treturn apply_save_autostash_oid(stash_oid, 1);\n     -+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n     ++\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n       }\n       \n       static int apply_save_autostash_ref(struct repository *r, const char *refname,\n      -\t\t\t\t    int attempt_apply)\n      +\t\t\t\t    int attempt_apply,\n      +\t\t\t\t    const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t    const char *label_base)\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg)\n       {\n       \tstruct object_id stash_oid;\n       \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n     @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n       \toid_to_hex_r(stash_oid_hex, &stash_oid);\n      -\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n      +\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n     -+\t\t\t\t       label_ours, label_theirs, label_base);\n     ++\t\t\t\t       label_ours, label_theirs, label_base,\n     ++\t\t\t\t       stash_msg);\n       \n       \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n       \t\t\t&stash_oid, REF_NO_DEREF);\n     @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n       int save_autostash_ref(struct repository *r, const char *refname)\n       {\n      -\treturn apply_save_autostash_ref(r, refname, 0);\n     -+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n     ++\treturn apply_save_autostash_ref(r, refname, 0,\n     ++\t\t\t\t\tNULL, NULL, NULL, NULL);\n       }\n       \n       int apply_autostash_ref(struct repository *r, const char *refname)\n       {\n      -\treturn apply_save_autostash_ref(r, refname, 1);\n     -+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n     ++\treturn apply_save_autostash_ref(r, refname, 1,\n     ++\t\t\t\t\tNULL, NULL, NULL, NULL);\n      +}\n      +\n      +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n      +\t\t\t\t    const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t    const char *label_base)\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg)\n      +{\n      +\treturn apply_save_autostash_ref(r, refname, 1,\n     -+\t\t\t\t\tlabel_ours, label_theirs, label_base);\n     ++\t\t\t\t\tlabel_ours, label_theirs, label_base,\n     ++\t\t\t\t\tstash_msg);\n       }\n       \n       static int checkout_onto(struct repository *r, struct replay_opts *opts,\n     @@ sequencer.h: int save_autostash_ref(struct repository *r, const char *refname);\n       int apply_autostash_ref(struct repository *r, const char *refname);\n      +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n      +\t\t\t\t    const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t    const char *label_base);\n     ++\t\t\t\t    const char *label_base,\n     ++\t\t\t\t    const char *stash_msg);\n       \n       #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n       #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n 4:  97a5d87c81 ! 4:  911e520431 checkout: -m (--merge) uses autostash when switching branches\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\t\tstrbuf_addf(&autostash_msg,\n      +\t\t\t\t    \"autostash while switching to '%s'\",\n      +\t\t\t\t    new_branch_info->name);\n     -+\t\t\tcreate_autostash_ref_silent_with_msg(the_repository,\n     -+\t\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n     -+\t\t\t\t\t\t\t    autostash_msg.buf);\n     ++\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n     ++\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n     ++\t\t\t\t\t\t   autostash_msg.buf);\n      +\t\t\tcreated_autostash = 1;\n      +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\t}\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       }\n      \n       ## sequencer.c ##\n     -@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n     - static void create_autostash_internal(struct repository *r,\n     - \t\t\t\t      const char *path,\n     - \t\t\t\t      const char *refname,\n     -+\t\t\t\t      const char *message,\n     - \t\t\t\t      bool silent)\n     - {\n     - \tstruct strbuf buf = STRBUF_INIT;\n     -@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     - \t\tstruct object_id oid;\n     - \n     - \t\tstrvec_pushl(&stash.args,\n     --\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n     -+\t\t\t     \"stash\", \"create\",\n     -+\t\t\t     message ? message : \"autostash\", NULL);\n     - \t\tstash.git_cmd = 1;\n     - \t\tstash.no_stdin = 1;\n     - \t\tstrbuf_reset(&buf);\n     -@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n     - \n     - void create_autostash(struct repository *r, const char *path)\n     - {\n     --\tcreate_autostash_internal(r, path, NULL, false);\n     -+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n     - }\n     - \n     - void create_autostash_ref(struct repository *r, const char *refname)\n     - {\n     --\tcreate_autostash_internal(r, NULL, refname, false);\n     -+\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n     - }\n     - \n     - void create_autostash_ref_silent(struct repository *r, const char *refname)\n     - {\n     --\tcreate_autostash_internal(r, NULL, refname, true);\n     -+\tcreate_autostash_internal(r, NULL, refname, NULL, true);\n     -+}\n     -+\n     -+void create_autostash_ref_silent_with_msg(struct repository *r,\n     -+\t\t\t\t\t  const char *refname,\n     -+\t\t\t\t\t  const char *message)\n     -+{\n     -+\tcreate_autostash_internal(r, NULL, refname, message, true);\n     - }\n     - \n     - static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     - \t\t\t\t    const char *label_ours, const char *label_theirs,\n     --\t\t\t\t    const char *label_base)\n     -+\t\t\t\t    const char *label_base,\n     -+\t\t\t\t    const char *stash_msg)\n     - {\n     - \tstruct child_process child = CHILD_PROCESS_INIT;\n     - \tint ret = 0;\n      @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n     - \t\tstrvec_push(&store.args, \"stash\");\n     - \t\tstrvec_push(&store.args, \"store\");\n     - \t\tstrvec_push(&store.args, \"-m\");\n     --\t\tstrvec_push(&store.args, \"autostash\");\n     -+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n     - \t\tstrvec_push(&store.args, \"-q\");\n       \t\tstrvec_push(&store.args, stash_oid);\n       \t\tif (run_command(&store))\n       \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t}\n       \n       \treturn ret;\n     -@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n     - \tstrbuf_trim(&stash_oid);\n     - \n     - \tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n     --\t\t\t\t      NULL, NULL, NULL);\n     -+\t\t\t\t      NULL, NULL, NULL, NULL);\n     - \n     - \tunlink(path);\n     - \tstrbuf_release(&stash_oid);\n     -@@ sequencer.c: int apply_autostash(const char *path)\n     - \n     - int apply_autostash_oid(const char *stash_oid)\n     - {\n     --\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n     -+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n     - }\n     - \n     - static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     - \t\t\t\t    int attempt_apply,\n     - \t\t\t\t    const char *label_ours, const char *label_theirs,\n     --\t\t\t\t    const char *label_base)\n     -+\t\t\t\t    const char *label_base,\n     -+\t\t\t\t    const char *stash_msg)\n     - {\n     - \tstruct object_id stash_oid;\n     - \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n     -@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     - \n     - \toid_to_hex_r(stash_oid_hex, &stash_oid);\n     - \tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n     --\t\t\t\t       label_ours, label_theirs, label_base);\n     -+\t\t\t\t       label_ours, label_theirs, label_base,\n     -+\t\t\t\t       stash_msg);\n     - \n     - \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n     - \t\t\t&stash_oid, REF_NO_DEREF);\n     -@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n     - \n     - int save_autostash_ref(struct repository *r, const char *refname)\n     - {\n     --\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n     -+\treturn apply_save_autostash_ref(r, refname, 0,\n     -+\t\t\t\t\tNULL, NULL, NULL, NULL);\n     - }\n     - \n     - int apply_autostash_ref(struct repository *r, const char *refname)\n     - {\n     --\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n     -+\treturn apply_save_autostash_ref(r, refname, 1,\n     -+\t\t\t\t\tNULL, NULL, NULL, NULL);\n     - }\n     - \n     - int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     - \t\t\t\t    const char *label_ours, const char *label_theirs,\n     --\t\t\t\t    const char *label_base)\n     -+\t\t\t\t    const char *label_base,\n     -+\t\t\t\t    const char *stash_msg)\n     - {\n     - \treturn apply_save_autostash_ref(r, refname, 1,\n     --\t\t\t\t\tlabel_ours, label_theirs, label_base);\n     -+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n     -+\t\t\t\t\tstash_msg);\n     - }\n     - \n     - static int checkout_onto(struct repository *r, struct replay_opts *opts,\n     -\n     - ## sequencer.h ##\n     -@@ sequencer.h: void commit_post_rewrite(struct repository *r,\n     - void create_autostash(struct repository *r, const char *path);\n     - void create_autostash_ref(struct repository *r, const char *refname);\n     - void create_autostash_ref_silent(struct repository *r, const char *refname);\n     -+void create_autostash_ref_silent_with_msg(struct repository *r,\n     -+\t\t\t\t\t  const char *refname,\n     -+\t\t\t\t\t  const char *message);\n     - int save_autostash(const char *path);\n     - int save_autostash_ref(struct repository *r, const char *refname);\n     - int apply_autostash(const char *path);\n     -@@ sequencer.h: int apply_autostash_oid(const char *stash_oid);\n     - int apply_autostash_ref(struct repository *r, const char *refname);\n     - int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     - \t\t\t\t    const char *label_ours, const char *label_theirs,\n     --\t\t\t\t    const char *label_base);\n     -+\t\t\t\t    const char *label_base,\n     -+\t\t\t\t    const char *stash_msg);\n     - \n     - #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n     - #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n      \n       ## t/t3420-rebase-autostash.sh ##\n      @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n\n-- \ngitgitgadget\n"},{"id":"541547","messageId":"9ab5431b4773c29097ae9bdd497822477c7ba56a.1776171585.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","subject":"[PATCH v12 1/4] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T12:59:42Z","receivedAt":"2026-04-14T12:59:50Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 32 +++++++++++++++++++++++++-------\n t/t3903-stash.sh             | 29 +++++++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 67 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..00314e2b13 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -590,8 +590,11 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n \t\tdie(_(\"could not write index\"));\n }\n \n-static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+static int do_apply_stash_with_labels(const char *prefix,\n+\t\t\t\t      struct stash_info *info,\n+\t\t\t\t      int index, int quiet,\n+\t\t\t\t      const char *label_ours, const char *label_theirs,\n+\t\t\t\t      const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +646,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -717,17 +720,31 @@ restore_untracked:\n \treturn ret;\n }\n \n+static int do_apply_stash(const char *prefix, struct stash_info *info,\n+\t\t\t  int index, int quiet)\n+{\n+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n+\t\t\t\t\t  NULL, NULL, NULL);\n+}\n+\n static int apply_stash(int argc, const char **argv, const char *prefix,\n \t\t       struct repository *repo UNUSED)\n {\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +754,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n+\t\t\t\t\t label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..00bcb1f802 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit init conflict_labels &&\n+\t(\n+\t\tcd conflict_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\t\ttest_grep \"^<<<<<<< UP\" file &&\n+\t\ttest_grep \"^||||||| Stash base\" file &&\n+\t\ttest_grep \"^>>>>>>> STASH\" file\n+\t)\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit init empty_labels &&\n+\t(\n+\t\tcd empty_labels &&\n+\t\ttest_commit base file &&\n+\t\techo stashed >file &&\n+\t\tgit stash push -m \"stashed\" &&\n+\t\ttest_commit upstream file &&\n+\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\t\ttest_grep \"^<<<<<<<$\" file &&\n+\t\ttest_grep \"^>>>>>>>$\" file\n+\t)\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541548","messageId":"e11a622bdf4752273dc2de26f3108d329abb04b3.1776171585.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","subject":"[PATCH v12 2/4] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T12:59:43Z","receivedAt":"2026-04-14T12:59:51Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 20 +++++++++++++++-----\n sequencer.h |  2 ++\n 2 files changed, 17 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..780628aab4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      const char *message,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n+\t\t\t     \"stash\", \"create\",\n+\t\t\t     message ? message : \"autostash\", NULL);\n \t\tstash.git_cmd = 1;\n \t\tstash.no_stdin = 1;\n \t\tstrbuf_reset(&buf);\n@@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4718,18 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n }\n \n void create_autostash_ref(struct repository *r, const char *refname)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n+}\n+\n+void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n+\t\t\t\t const char *message)\n+{\n+\tcreate_autostash_internal(r, NULL, refname, message, true);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..5d3bc83314 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,6 +230,8 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n+\t\t\t\t const char *message);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541549","messageId":"4593745e90d7c897463fcb4835ecfac70a088b8e.1776171585.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","subject":"[PATCH v12 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T12:59:44Z","receivedAt":"2026-04-14T12:59:53Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, and label_base parameters to the autostash\napply machinery so callers can pass custom conflict marker labels\nthrough to \"git stash apply --label-ours/--label-theirs/--label-base\".\nIntroduce apply_autostash_ref_with_labels() for callers that want\nto pass labels.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n sequencer.c | 43 +++++++++++++++++++++++++++++++++++--------\n sequencer.h |  4 ++++\n 2 files changed, 39 insertions(+), 8 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 780628aab4..c2516000bd 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4732,7 +4732,10 @@ void create_autostash_ref_with_msg_silent(struct repository *r, const char *refn\n \tcreate_autostash_internal(r, NULL, refname, message, true);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4743,6 +4746,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4756,7 +4765,7 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tstrvec_push(&store.args, \"stash\");\n \t\tstrvec_push(&store.args, \"store\");\n \t\tstrvec_push(&store.args, \"-m\");\n-\t\tstrvec_push(&store.args, \"autostash\");\n+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n \t\tstrvec_push(&store.args, \"-q\");\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n@@ -4787,7 +4796,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4806,11 +4816,14 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4826,7 +4839,9 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base,\n+\t\t\t\t       stash_msg);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4836,12 +4851,24 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n int apply_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n+}\n+\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n+{\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n+\t\t\t\t\tstash_msg);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 5d3bc83314..b0c891d3b6 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -237,6 +237,10 @@ int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541550","messageId":"911e520431e9923d0b5795532e2abe794477707d.1776171585.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","subject":"[PATCH v12 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-14T12:59:45Z","receivedAt":"2026-04-14T12:59:55Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", local modifications\ncan block the switch.  Teach the -m flow to create a temporary stash\nbefore switching and reapply it after.  On success, only \"Applied\nautostash.\" is shown.  If reapplying causes conflicts, the stash is\nkept and the user is told they can resolve and run \"git stash drop\",\nor run \"git reset --hard\" and later \"git stash pop\" to recover their\nchanges.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 147 ++++++++++------------\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 9 files changed, 368 insertions(+), 135 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..a7a93e1c6b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge)\n-\t\t\t\treturn 1;\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n-\t\t\t\treturn 1;\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n-\t\t\t\treturn ret;\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn 1;\n \t\t}\n \t}\n \n@@ -1166,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tstruct strbuf autostash_msg = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1203,10 +1140,37 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_base = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret && opts->merge) {\n+\t\t\tstrbuf_addf(&autostash_msg,\n+\t\t\t\t    \"autostash while switching to '%s'\",\n+\t\t\t\t    new_branch_info->name);\n+\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t   autostash_msg.buf);\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tapply_autostash_ref_with_labels(the_repository,\n+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t       new_branch_info->name,\n+\t\t\t\t\t\t       \"local\",\n+\t\t\t\t\t\t       stash_label_base,\n+\t\t\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n \t\t\tbranch_info_release(&old_branch_info);\n+\t\t\tstrbuf_release(&old_commit_shortname);\n+\t\t\tstrbuf_release(&autostash_msg);\n \t\t\treturn ret;\n \t\t}\n \t}\n@@ -1216,8 +1180,31 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t       new_branch_info->name, \"local\",\n+\t\t\t\t       stash_label_base,\n+\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n+\t    new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n+\tstrbuf_release(&autostash_msg);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex c2516000bd..b78a8ff092 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4770,15 +4770,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..c474c6759f 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m autostash message includes target branch' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\tgit stash list >stash-list &&\n+\ttest_grep \"autostash while switching to .side.\" stash-list &&\n+\tgit stash drop &&\n+\tgit checkout -f main &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541551","messageId":"xmqqmrz5eklt.fsf@gitster.g","threadId":"65220","inReplyTo":"20260414072950.61240-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T13:29:18Z","receivedAt":"2026-04-14T13:29:21Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> Sounds reasonable, but wouldn't it make more sense to call it \"autostash\n> from master\". We should still be able to abort the merge and merge it to\n> some other branch. I feel like the source is more relevant than the\n> destination, no?\n\nThe new comment is for reminder, so \"I made this while switching\nfrom 'master' to this new 'topic'\" theoretically has more reminding\nvalue than \"I made this while switching to this new 'topic'\".  As I\noutlined my workflow, I usually am on 'master' or 'next' when I end\nup needing \"co -m\" option, so \"I was on 'master' when I stashed\nthis\" has a much weaker reminding value.  Just like a series of\n\"autostash\" without any context comment irritated me, I'll see many\n\"autostash on master\" that I cannot quite distinguish.\n\nBut that may be just me.\n"},{"id":"541554","messageId":"d5a47638-545b-44b3-9da5-803c06b3f98a@gmail.com","threadId":"65220","inReplyTo":"9ab5431b4773c29097ae9bdd497822477c7ba56a.1776171585.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v12 1/4] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-14T14:05:39Z","receivedAt":"2026-04-14T14:05:43Z","isPatch":true,"body":"Hi Harald\n\nOn 14/04/2026 13:59, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Allow callers of \"git stash apply\" to pass custom labels for conflict\n> markers instead of the default \"Updated upstream\" and \"Stashed changes\".\n> Document the new options and add a test.\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> [...]\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index 0d27b2fb1f..00314e2b13 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -44,7 +44,7 @@\n> [...]\n> -static int do_apply_stash(const char *prefix, struct stash_info *info,\n> -\t\t\t  int index, int quiet)\n> +static int do_apply_stash_with_labels(const char *prefix,\n> +\t\t\t\t      struct stash_info *info,\n> +\t\t\t\t      int index, int quiet,\n> +\t\t\t\t      const char *label_ours, const char *label_theirs,\n> +\t\t\t\t      const char *label_base)\n\nThere are only four callers of do_apply_stash so it might be better just \nto change the function signature and update the existing callers rather \nthan adding another function.\n\n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 70879941c2..00bcb1f802 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n>   \t)\n>   '\n>   \n> +test_expect_success 'apply with custom conflict labels' '\n> +\tgit init conflict_labels &&\n> +\t(\n\nI'm still unclear why we're creating a new repository here. Our test \nsuite is slow enough already without each test spending time creating \nits own repository. There doesn't seem to be anything here that requires \nisolating the test in this way.\n\nApart from that everything else looks good to me\n\nThanks\n\nPhillip\n\n> +\t\tcd conflict_labels &&\n> +\t\ttest_commit base file &&\n> +\t\techo stashed >file &&\n> +\t\tgit stash push -m \"stashed\" &&\n> +\t\ttest_commit upstream file &&\n> +\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n> +\t\ttest_grep \"^<<<<<<< UP\" file &&\n> +\t\ttest_grep \"^||||||| Stash base\" file &&\n> +\t\ttest_grep \"^>>>>>>> STASH\" file\n> +\t)\n> +'\n> +\n> +test_expect_success 'apply with empty conflict labels' '\n> +\tgit init empty_labels &&\n> +\t(\n> +\t\tcd empty_labels &&\n> +\t\ttest_commit base file &&\n> +\t\techo stashed >file &&\n> +\t\tgit stash push -m \"stashed\" &&\n> +\t\ttest_commit upstream file &&\n> +\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n> +\t\ttest_grep \"^<<<<<<<$\" file &&\n> +\t\ttest_grep \"^>>>>>>>$\" file\n> +\t)\n> +'\n> +\n>   test_expect_success 'stash create reports a locked index' '\n>   \ttest_when_finished \"rm -rf repo\" &&\n>   \tgit init repo &&\n> diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\n> index 29dad98c49..659ad4ec97 100644\n> --- a/xdiff/xmerge.c\n> +++ b/xdiff/xmerge.c\n> @@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n>   \t\t\t      int size, int i, int style,\n>   \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n>   {\n> -\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n> -\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n> -\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n> +\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n> +\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n> +\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n>   \tint needs_cr = is_cr_needed(xe1, xe2, m);\n>   \n>   \tif (marker_size <= 0)\n\n"},{"id":"541555","messageId":"e651e491-e59f-462a-9e81-35546112ce57@gmail.com","threadId":"65220","inReplyTo":"e11a622bdf4752273dc2de26f3108d329abb04b3.1776171585.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v12 2/4] sequencer: allow create_autostash to run silently","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-14T14:06:23Z","receivedAt":"2026-04-14T14:06:27Z","isPatch":true,"body":"Hi Harald\n\nOn 14/04/2026 13:59, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Add a silent parameter to create_autostash_internal and introduce\n> create_autostash_ref_silent so that callers can create an autostash\n> without printing the \"Created autostash\" message.\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n\nI wonder if we should just update the two callers of \ncreate_autostash_ref() instead of adding a new function but the \nimplementation looks sensible\n\nThanks\n\nPhillip\n\n>   sequencer.c | 20 +++++++++++++++-----\n>   sequencer.h |  2 ++\n>   2 files changed, 17 insertions(+), 5 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index b7d8dca47f..780628aab4 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n>   \n>   static void create_autostash_internal(struct repository *r,\n>   \t\t\t\t      const char *path,\n> -\t\t\t\t      const char *refname)\n> +\t\t\t\t      const char *refname,\n> +\t\t\t\t      const char *message,\n> +\t\t\t\t      bool silent)\n>   {\n>   \tstruct strbuf buf = STRBUF_INIT;\n>   \tstruct lock_file lock_file = LOCK_INIT;\n> @@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n>   \t\tstruct object_id oid;\n>   \n>   \t\tstrvec_pushl(&stash.args,\n> -\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n> +\t\t\t     \"stash\", \"create\",\n> +\t\t\t     message ? message : \"autostash\", NULL);\n>   \t\tstash.git_cmd = 1;\n>   \t\tstash.no_stdin = 1;\n>   \t\tstrbuf_reset(&buf);\n> @@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n>   \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n>   \t\t}\n>   \n> -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n> +\t\tif (!silent)\n> +\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n>   \t\tif (reset_head(r, &ropts) < 0)\n>   \t\t\tdie(_(\"could not reset --hard\"));\n>   \t\tdiscard_index(r->index);\n> @@ -4714,12 +4718,18 @@ static void create_autostash_internal(struct repository *r,\n>   \n>   void create_autostash(struct repository *r, const char *path)\n>   {\n> -\tcreate_autostash_internal(r, path, NULL);\n> +\tcreate_autostash_internal(r, path, NULL, NULL, false);\n>   }\n>   \n>   void create_autostash_ref(struct repository *r, const char *refname)\n>   {\n> -\tcreate_autostash_internal(r, NULL, refname);\n> +\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n> +}\n> +\n> +void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n> +\t\t\t\t const char *message)\n> +{\n> +\tcreate_autostash_internal(r, NULL, refname, message, true);\n>   }\n>   \n>   static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n> diff --git a/sequencer.h b/sequencer.h\n> index a6fa670c7c..5d3bc83314 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -230,6 +230,8 @@ void commit_post_rewrite(struct repository *r,\n>   \n>   void create_autostash(struct repository *r, const char *path);\n>   void create_autostash_ref(struct repository *r, const char *refname);\n> +void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n> +\t\t\t\t const char *message);\n>   int save_autostash(const char *path);\n>   int save_autostash_ref(struct repository *r, const char *refname);\n>   int apply_autostash(const char *path);\n\n"},{"id":"541556","messageId":"ebb65b1d-879f-4d9f-b25e-fe1c58a47ff7@gmail.com","threadId":"65220","inReplyTo":"4593745e90d7c897463fcb4835ecfac70a088b8e.1776171585.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v12 3/4] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-14T14:06:39Z","receivedAt":"2026-04-14T14:06:41Z","isPatch":true,"body":"Hi Harald\n\nOn 14/04/2026 13:59, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Add label_ours, label_theirs, and label_base parameters to the autostash\n> apply machinery so callers can pass custom conflict marker labels\n> through to \"git stash apply --label-ours/--label-theirs/--label-base\".\n> Introduce apply_autostash_ref_with_labels() for callers that want\n> to pass labels.\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> [...]\n> diff --git a/sequencer.c b/sequencer.c\n> index 780628aab4..c2516000bd 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4732,7 +4732,10 @@ void create_autostash_ref_with_msg_silent(struct repository *r, const char *refn\n>   \tcreate_autostash_internal(r, NULL, refname, message, true);\n>   }\n>   \n> -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n> +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n> +\t\t\t\t    const char *label_ours, const char *label_theirs,\n> +\t\t\t\t    const char *label_base,\n> +\t\t\t\t    const char *stash_msg)\n\nI'm confused why to need to provide a message when we're storing an \nexisting stash that already has the message that we want to use.\n> diff --git a/sequencer.h b/sequencer.h\n> index 5d3bc83314..b0c891d3b6 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -237,6 +237,10 @@ int save_autostash_ref(struct repository *r, const char *refname);\n>   int apply_autostash(const char *path);\n>   int apply_autostash_oid(const char *stash_oid);\n>   int apply_autostash_ref(struct repository *r, const char *refname);\n> +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n> +\t\t\t\t    const char *label_ours, const char *label_theirs,\n> +\t\t\t\t    const char *label_base,\n> +\t\t\t\t    const char *stash_msg);\n\nDo we need a new function, or can we just update the existing callers to \npass NULL? In the future I think we'll want to pass better conflict \nlabels and updating them now to pass NULL makes that easier.\n\nThanks\n\nPhillip\n"},{"id":"541557","messageId":"f012cc7e-14fa-40d2-84dc-7407fdceb36d@gmail.com","threadId":"65220","inReplyTo":"911e520431e9923d0b5795532e2abe794477707d.1776171585.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v12 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-14T14:07:01Z","receivedAt":"2026-04-14T14:07:05Z","isPatch":true,"body":"Hi Harald\n\nFor the subject line I think\n\n     checkout -m: autostash when switching branches\n\nwould be more in keeping with our usual style.\n\nOn 14/04/2026 13:59, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> When switching branches with \"git checkout -m\", local modifications\n> can block the switch. \n\nReally? Isn't the point of \"checkout -m\" to merge the local \nmodifications into the branch that's being checked out?\n\n> Teach the -m flow to create a temporary stash\n> before switching and reapply it after.  On success, only \"Applied\n> autostash.\" is shown. \n\nand a diff of the local changes?\n\n> If reapplying causes conflicts, the stash is\n> kept and the user is told they can resolve and run \"git stash drop\",\n> or run \"git reset --hard\" and later \"git stash pop\" to recover their\n> changes.\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n\nI've skipped the docs as I'm short on time\n\n> @@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \tstruct tree *new_tree;\n>   \n>   \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n> -\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n> +\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n> +\t\trollback_lock_file(&lock_file);\n>   \t\treturn error(_(\"index file corrupt\"));\n> +\t}\n>   \n>   \tresolve_undo_clear_index(the_repository->index);\n>   \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n> @@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t} else {\n>   \t\tnew_tree = repo_get_commit_tree(the_repository,\n>   \t\t\t\t\t\tnew_branch_info->commit);\n> -\t\tif (!new_tree)\n> +\t\tif (!new_tree) {\n> +\t\t\trollback_lock_file(&lock_file);\n>   \t\t\treturn error(_(\"unable to read tree (%s)\"),\n>   \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n> +\t\t}\n>   \t}\n>   \tif (opts->discard_changes) {\n>   \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n> -\t\tif (ret)\n> +\t\tif (ret) {\n> +\t\t\trollback_lock_file(&lock_file);\n>   \t\t\treturn ret;\n> +\t\t}\n>   \t} else {\n>   \t\tstruct tree_desc trees[2];\n>   \t\tstruct tree *tree;\n> @@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n>   \n>   \t\tif (unmerged_index(the_repository->index)) {\n> +\t\t\trollback_lock_file(&lock_file);\n>   \t\t\terror(_(\"you need to resolve your current index first\"));\n>   \t\t\treturn 1;\n\nThe changes up to here look like fixes for an existing bug and so would \nbe better in a separate patch.\n\nSometimes we return \"1\" and sometimes \"-1\" what does that signal to the \ncaller?\n\n>   \t\t}\n> @@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\tret = unpack_trees(2, trees, &topts);\n>   \t\tclear_unpack_trees_porcelain(&topts);\n>   \t\tif (ret == -1) {\n> [lots of deletions]\n> -\t\t\tif (ret)\n> -\t\t\t\treturn ret;\n> +\t\t\trollback_lock_file(&lock_file);\n> +\t\t\treturn 1;\n>   \t\t}\n>   \t}\n>   \n> @@ -1166,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \tstruct object_id rev;\n>   \tint flag, writeout_error = 0;\n>   \tint do_merge = 1;\n> +\tint created_autostash = 0;\n\nThis can be a bool\n\n> +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n> +\tstruct strbuf autostash_msg = STRBUF_INIT;\n> +\tconst char *stash_label_base = NULL;\n>   \n>   \ttrace2_cmd_mode(\"branch\");\n>   \n> @@ -1203,10 +1140,37 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \t\t\tdo_merge = 0;\n>   \t}\n>   \n> +\tif (old_branch_info.name)\n> +\t\tstash_label_base = old_branch_info.name;\n> +\telse if (old_branch_info.commit) {\n> +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n> +\t\t\t\t\t &old_branch_info.commit->object.oid,\n> +\t\t\t\t\t DEFAULT_ABBREV);\n> +\t\tstash_label_base = old_commit_shortname.buf;\n> +\t}\n> +\n>   \tif (do_merge) {\n>   \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\tif (ret && opts->merge) {\n\nAs we saw above merge_working_tree() can return non-zero for a variety \nof reasons. We only want to try stashing if the call to unpack_trees() \nfailed. Even then if you look at the list of errors in unpack-trees.h \nyou'll see that only a few of them relate to problems that can be solved \nby stashing. The old code just tried merging whenever unpack_trees() \nfailed so it probably not so bad to do the same here but we should not \nbe stashing if merge_working_tree() returns before calling unpack_trees().\n\n> +\t\t\tstrbuf_addf(&autostash_msg,\n> +\t\t\t\t    \"autostash while switching to '%s'\",\n> +\t\t\t\t    new_branch_info->name);\n> +\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n> +\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n\nIt's a shame we have to create a ref here. MERGE_AUTOSTASH exists so \nthat \"git merge --continue\" can apply the stash once the user has \nresolved any merge conflicts. We don't have that problem here because \nthere is no user interaction and we could just hold onto the stash oid \nin a variable.\n\n> +\t\t\t\t\t\t   autostash_msg.buf);\n> +\t\t\tcreated_autostash = 1;\n> +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\t}\n>   \t\tif (ret) {\n\nI'm confused by this - if we stash then don't we expect the call to \nunpack_trees() in merge_working_tree() to succeed and therefore return \n0? If opts->merge is false then we should not be trying to apply the \nstash when merge_working_tree() fails.\n\n> +\t\t\tapply_autostash_ref_with_labels(the_repository,\n> +\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t\t       new_branch_info->name,\n> +\t\t\t\t\t\t       \"local\",\n> +\t\t\t\t\t\t       stash_label_base,\n> +\t\t\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n\nCan we create an autostash without setting a message in autostash_msg?\n\n>   \t\t\tbranch_info_release(&old_branch_info);\n> +\t\t\tstrbuf_release(&old_commit_shortname);\n> +\t\t\tstrbuf_release(&autostash_msg);\n>   \t\t\treturn ret;\n>   \t\t}\n>   \t}\n> @@ -1216,8 +1180,31 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \n>   \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n>   \n> +\tif (opts->conflict_style >= 0) {\n> +\t\tstruct strbuf cfg = STRBUF_INIT;\n> +\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n> +\t\t\t    conflict_style_name(opts->conflict_style));\n> +\t\tgit_config_push_parameter(cfg.buf);\n\nThis is rather unfortunate - it might be nicer for \ncreate_autostash_internal() to set the conflict style.\n\n> +\t\tstrbuf_release(&cfg);\n> +\t}\n> +\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t       new_branch_info->name, \"local\",\n> +\t\t\t\t       stash_label_base,\n> +\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n> +\n> +\tdiscard_index(the_repository->index);\n> +\tif (repo_read_index(the_repository) < 0)\n> +\t\tdie(_(\"index file corrupt\"));\n> +\n> +\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n\nWouldn't it be a bug if we've created and autostash when \nopts->discard_changes is set? Why do we need to check it?\n\n> +\t    new_branch_info->commit)\n> +\t\tshow_local_changes(&new_branch_info->commit->object,\n> +\t\t\t\t   &opts->diff_options);\n\nSo this is a change to the output when using \"checkout -m\"? If so it \nmight be better as a separate change.\n\n\nI'll have to leave it there for now, I'll try and look at the rest of \nthe changes later in the week.\n\nThanks\n\nPhillip\n\n> +\n>   \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n>   \tbranch_info_release(&old_branch_info);\n> +\tstrbuf_release(&old_commit_shortname);\n> +\tstrbuf_release(&autostash_msg);\n>   \n>   \treturn ret || writeout_error;\n>   }\n> diff --git a/sequencer.c b/sequencer.c\n> index c2516000bd..b78a8ff092 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4770,15 +4770,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n>   \t\tstrvec_push(&store.args, stash_oid);\n>   \t\tif (run_command(&store))\n>   \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n> +\t\telse if (attempt_apply)\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n> +\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n> +\t\t\t\t  \"\\n\"\n> +\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n> +\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n> +\t\t\t\t  \"\\n\"\n> +\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n> +\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n> +\t\t\t\t  \"   recover your local changes.\\n\"));\n>   \t\telse\n>   \t\t\tfprintf(stderr,\n> -\t\t\t\t_(\"%s\\n\"\n> +\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n>   \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n>   \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n> -\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n> -\t\t\t\tattempt_apply ?\n> -\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n> -\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n> +\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n>   \t}\n>   \n>   \treturn ret;\n> diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\n> index ad3ba6a984..e4e2cb19ce 100755\n> --- a/t/t3420-rebase-autostash.sh\n> +++ b/t/t3420-rebase-autostash.sh\n> @@ -61,18 +61,30 @@ create_expected_failure_apply () {\n>   \tFirst, rewinding head to replay your work on top of it...\n>   \tApplying: second commit\n>   \tApplying: third commit\n> -\tApplying autostash resulted in conflicts.\n> -\tYour changes are safe in the stash.\n> -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n> +\tYour local changes are stashed, however, applying it to carry\n> +\tforward your local changes resulted in conflicts:\n> +\n> +\t - You can try resolving them now.  If you resolved them\n> +\t   successfully, discard the stash entry with \"git stash drop\".\n> +\n> +\t - Alternatively you can \"git reset --hard\" if you do not want\n> +\t   to deal with them right now, and later \"git stash pop\" to\n> +\t   recover your local changes.\n>   \tEOF\n>   }\n>   \n>   create_expected_failure_merge () {\n>   \tcat >expected <<-EOF\n>   \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n> -\tApplying autostash resulted in conflicts.\n> -\tYour changes are safe in the stash.\n> -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n> +\tYour local changes are stashed, however, applying it to carry\n> +\tforward your local changes resulted in conflicts:\n> +\n> +\t - You can try resolving them now.  If you resolved them\n> +\t   successfully, discard the stash entry with \"git stash drop\".\n> +\n> +\t - Alternatively you can \"git reset --hard\" if you do not want\n> +\t   to deal with them right now, and later \"git stash pop\" to\n> +\t   recover your local changes.\n>   \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n>   \tEOF\n>   }\n> diff --git a/t/t7201-co.sh b/t/t7201-co.sh\n> index 9bcf7c0b40..c474c6759f 100755\n> --- a/t/t7201-co.sh\n> +++ b/t/t7201-co.sh\n> @@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n>   \ttest_cmp expect two\n>   '\n>   \n> +test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\tfill a b X d e >two &&\n> +\tgit checkout --merge --conflict=zdiff3 simple &&\n> +\n> +\tcat <<-EOF >expect &&\n> +\ta\n> +\t<<<<<<< simple\n> +\tc\n> +\t||||||| main\n> +\tb\n> +\tc\n> +\td\n> +\t=======\n> +\tb\n> +\tX\n> +\td\n> +\t>>>>>>> local\n> +\te\n> +\tEOF\n> +\ttest_cmp expect two\n> +'\n> +\n> +test_expect_success 'checkout -m respects merge.conflictStyle config' '\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\ttest_config merge.conflictStyle diff3 &&\n> +\tfill b d >two &&\n> +\tgit checkout -m simple &&\n> +\n> +\tcat <<-EOF >expect &&\n> +\t<<<<<<< simple\n> +\ta\n> +\tc\n> +\te\n> +\t||||||| main\n> +\ta\n> +\tb\n> +\tc\n> +\td\n> +\te\n> +\t=======\n> +\tb\n> +\td\n> +\t>>>>>>> local\n> +\tEOF\n> +\ttest_cmp expect two\n> +'\n> +\n> +test_expect_success 'checkout -m skips stash when no conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit stash list >stash-before &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tgit stash list >stash-after &&\n> +\ttest_cmp stash-before stash-after &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same\n> +'\n> +\n> +test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit add same &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same\n> +'\n> +\n> +test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 6 7 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"Applied autostash\" actual &&\n> +\tfill 1 2 3 4 5 6 7 >expect &&\n> +\ttest_cmp expect one\n> +'\n> +\n> +test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit add same &&\n> +\tfill 1 2 3 4 5 6 7 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"Applied autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same &&\n> +\tfill 1 2 3 4 5 6 7 >expect &&\n> +\ttest_cmp expect one\n> +'\n> +\n> +test_expect_success 'checkout -m stashes on truly conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\ttest_must_fail git checkout side 2>stderr &&\n> +\ttest_grep \"Your local changes\" stderr &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"resulted in conflicts\" actual &&\n> +\ttest_grep \"git stash drop\" actual &&\n> +\tgit stash drop &&\n> +\tgit reset --hard\n> +'\n> +\n> +test_expect_success 'checkout -m produces usable stash on conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep \"recover your local changes\" actual &&\n> +\tgit checkout -f main &&\n> +\tgit stash pop &&\n> +\tfill 1 2 3 4 5 >expect &&\n> +\ttest_cmp expect one\n> +'\n> +\n> +test_expect_success 'checkout -m autostash message includes target branch' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\tgit stash list >stash-list &&\n> +\ttest_grep \"autostash while switching to .side.\" stash-list &&\n> +\tgit stash drop &&\n> +\tgit checkout -f main &&\n> +\tgit reset --hard\n> +'\n> +\n> +test_expect_success 'checkout -m stashes on staged conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit add one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"resulted in conflicts\" actual &&\n> +\ttest_grep \"git stash drop\" actual &&\n> +\tgit stash drop &&\n> +\tgit reset --hard\n> +'\n> +\n> +test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\tgit checkout -b nonoverlap_base &&\n> +\tfill a b c d >file &&\n> +\tgit add file &&\n> +\tgit commit -m \"add file\" &&\n> +\n> +\tgit checkout -b nonoverlap_child &&\n> +\tfill a b c INSERTED d >file &&\n> +\tgit commit -a -m \"insert line near end of file\" &&\n> +\n> +\tfill DIRTY a b c INSERTED d >file &&\n> +\n> +\tgit stash list >stash-before &&\n> +\tgit checkout -m nonoverlap_base 2>stderr &&\n> +\ttest_grep \"Applied autostash\" stderr &&\n> +\ttest_grep ! \"resulted in conflicts\" stderr &&\n> +\n> +\tgit stash list >stash-after &&\n> +\ttest_cmp stash-before stash-after &&\n> +\n> +\tfill DIRTY a b c d >expect &&\n> +\ttest_cmp expect file &&\n> +\n> +\tgit checkout -f main &&\n> +\tgit branch -D nonoverlap_base &&\n> +\tgit branch -D nonoverlap_child\n> +'\n> +\n> +test_expect_success 'checkout -m -b skips stash with dirty tree' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit checkout -m -b newbranch >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same &&\n> +\tgit checkout main &&\n> +\tgit branch -D newbranch\n> +'\n> +\n>   test_expect_success 'switch to another branch while carrying a deletion' '\n>   \tgit checkout -f main &&\n>   \tgit reset --hard &&\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index 9838094b66..cbef8a534e 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n>   \tgit diff >expect &&\n>   \ttest_when_finished \"test_might_fail git stash drop\" &&\n>   \tgit merge --autostash c3 2>err &&\n> -\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n> +\ttest_grep \"your local changes resulted in conflicts\" err &&\n>   \tgit show HEAD:file >merge-result &&\n>   \ttest_cmp result.1-9 merge-result &&\n>   \tgit stash show -p >actual &&\n> diff --git a/xdiff-interface.c b/xdiff-interface.c\n> index f043330f2a..5ee2b96d0a 100644\n> --- a/xdiff-interface.c\n> +++ b/xdiff-interface.c\n> @@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n>   \t\treturn -1;\n>   }\n>   \n> +const char *conflict_style_name(int style)\n> +{\n> +\tswitch (style) {\n> +\tcase XDL_MERGE_DIFF3:\n> +\t\treturn \"diff3\";\n> +\tcase XDL_MERGE_ZEALOUS_DIFF3:\n> +\t\treturn \"zdiff3\";\n> +\tdefault:\n> +\t\treturn \"merge\";\n> +\t}\n> +}\n> +\n>   int git_xmerge_style = -1;\n>   \n>   int git_xmerge_config(const char *var, const char *value,\n> diff --git a/xdiff-interface.h b/xdiff-interface.h\n> index fbc4ceec40..ce54e1c0e0 100644\n> --- a/xdiff-interface.h\n> +++ b/xdiff-interface.h\n> @@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n>   void xdiff_clear_find_func(xdemitconf_t *xecfg);\n>   struct config_context;\n>   int parse_conflict_style_name(const char *value);\n> +const char *conflict_style_name(int style);\n>   int git_xmerge_config(const char *var, const char *value,\n>   \t\t      const struct config_context *ctx, void *cb);\n>   extern int git_xmerge_style;\n\n"},{"id":"541558","messageId":"xmqq7bq9eii9.fsf@gitster.g","threadId":"65220","inReplyTo":"xmqqmrz5eklt.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T14:14:38Z","receivedAt":"2026-04-14T14:14:41Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Harald Nordgren <haraldnordgren@gmail.com> writes:\n>\n>> Sounds reasonable, but wouldn't it make more sense to call it \"autostash\n>> from master\". We should still be able to abort the merge and merge it to\n>> some other branch. I feel like the source is more relevant than the\n>> destination, no?\n>\n> The new comment is for reminder, so \"I made this while switching\n> from 'master' to this new 'topic'\" theoretically has more reminding\n> value than \"I made this while switching to this new 'topic'\".  As I\n> outlined my workflow, I usually am on 'master' or 'next' when I end\n> up needing \"co -m\" option, so \"I was on 'master' when I stashed\n> this\" has a much weaker reminding value.  Just like a series of\n> \"autostash\" without any context comment irritated me, I'll see many\n> \"autostash on master\" that I cannot quite distinguish.\n>\n> But that may be just me.\n\nIn any case, the topic is already in 'next' and this kind of minor\ntweaks are best done as a separate topic once the basic framework\nthat works reasonably well is established on top.  We may end up\nwanting some mechanism to customize the message in the end but that\nis something we will find out and become able to decide on the best\ndesign only after we let users use it for a while.\n\nThanks.\n"},{"id":"541566","messageId":"xmqqfr4xcz7s.fsf@gitster.g","threadId":"65220","inReplyTo":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","subject":"Re: [PATCH v12 0/4] checkout: 'autostash' for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T15:56:39Z","receivedAt":"2026-04-14T15:56:42Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Harald Nordgren (4):\n>   stash: add --label-ours, --label-theirs, --label-base for apply\n>   sequencer: allow create_autostash to run silently\n>   sequencer: teach autostash apply to take optional conflict marker\n>     labels\n>   checkout: -m (--merge) uses autostash when switching branches\n>\n>  Documentation/git-checkout.adoc |  58 ++++-----\n>  Documentation/git-stash.adoc    |  11 +-\n>  Documentation/git-switch.adoc   |  33 ++---\n>  builtin/checkout.c              | 147 ++++++++++------------\n>  builtin/stash.c                 |  32 +++--\n>  sequencer.c                     |  81 ++++++++++---\n>  sequencer.h                     |   6 +\n>  t/t3420-rebase-autostash.sh     |  24 +++-\n>  t/t3903-stash.sh                |  29 +++++\n>  t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n>  t/t7600-merge.sh                |   2 +-\n>  xdiff-interface.c               |  12 ++\n>  xdiff-interface.h               |   1 +\n>  xdiff/xmerge.c                  |   6 +-\n>  14 files changed, 491 insertions(+), 159 deletions(-)\n\nI am not taking v11 and v12 today, as v10 is already in 'next', but\nfor future reference, I have to say that this cover letter is not\nvery helpful.\n\nIt shows 350+ lines of range-diff to show mostly irrelevant noise,\nwhen the true difference between v11 and v12 is only that two helper\nfunctions create_autostash_ref_silent{,_with_msg}() are merged into\none create_autostash_ref_with_msg_silent() helper function.\n\nIt is much easier to read that read from the diff between the\nresults of applying v11 and v12 on the same base commit, which is a\nmere 55 lines (shown at the end).\n\nI would not expect you to teach GGG to produce a better range-diff\nor add an option to instead show an interdiff, but doesn't GGG\nalready have a way to add some human-written comment, e.g.,\n\n    Changes since v11:\n\n        Two helper functions create_autostash_ref_silent{,_with_msg}()\n        are merged into one create_autostash_ref_with_msg_silent().\n\nto the cover letter?  Such a comment, even a very short one like I\nshowed above, would be a lot more effective way to help human\nreaders to decide what is the point of this iteration is, especially\nwhen you are sending v11 and v12 almost back-to-back, and to decide\nif it is easier to understand to read both or if v11 can be skipped\nand reading v12 alone (with the understanding of the difference\nexplained in the comment) should be enough to tell if v12 is an\nimprovement over v11 or not.\n\nhttps://github.com/gitgitgadget/gitgitgadget/blob/main/DESIGN.md#patch-submissions\n\nsays that\n\n    The description of the Pull Request will be used as cover\n    letter, ...\n\nso perhaps your pull-request comment should have something more than\njust the list of CC: recipients?\n\nThanks.\n\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 2adfefa085..a7a93e1c6b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1155,9 +1155,9 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tstrbuf_addf(&autostash_msg,\n \t\t\t\t    \"autostash while switching to '%s'\",\n \t\t\t\t    new_branch_info->name);\n-\t\t\tcreate_autostash_ref_silent_with_msg(the_repository,\n-\t\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n-\t\t\t\t\t\t\t    autostash_msg.buf);\n+\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t   autostash_msg.buf);\n \t\t\tcreated_autostash = 1;\n \t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n \t\t}\ndiff --git a/sequencer.c b/sequencer.c\nindex febdb5b20a..b78a8ff092 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4726,14 +4726,8 @@ void create_autostash_ref(struct repository *r, const char *refname)\n \tcreate_autostash_internal(r, NULL, refname, NULL, false);\n }\n \n-void create_autostash_ref_silent(struct repository *r, const char *refname)\n-{\n-\tcreate_autostash_internal(r, NULL, refname, NULL, true);\n-}\n-\n-void create_autostash_ref_silent_with_msg(struct repository *r,\n-\t\t\t\t\t  const char *refname,\n-\t\t\t\t\t  const char *message)\n+void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n+\t\t\t\t const char *message)\n {\n \tcreate_autostash_internal(r, NULL, refname, message, true);\n }\ndiff --git a/sequencer.h b/sequencer.h\nindex 67de755053..b0c891d3b6 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -230,10 +230,8 @@ void commit_post_rewrite(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path);\n void create_autostash_ref(struct repository *r, const char *refname);\n-void create_autostash_ref_silent(struct repository *r, const char *refname);\n-void create_autostash_ref_silent_with_msg(struct repository *r,\n-\t\t\t\t\t  const char *refname,\n-\t\t\t\t\t  const char *message);\n+void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n+\t\t\t\t const char *message);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n\n\n> base-commit: 9e8f4e9c04e3efa494e78b710e0c5f6cc77a0a5e\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v12\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v12\n> Pull-Request: https://github.com/git/git/pull/2234\n>\n> Range-diff vs v11:\n>\n>  1:  9ab5431b47 = 1:  9ab5431b47 stash: add --label-ours, --label-theirs, --label-base for apply\n>  2:  e7f8328e3c ! 2:  e11a622bdf sequencer: allow create_autostash to run silently\n>      @@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list,\n>        \t\t\t\t      const char *path,\n>       -\t\t\t\t      const char *refname)\n>       +\t\t\t\t      const char *refname,\n>      ++\t\t\t\t      const char *message,\n>       +\t\t\t\t      bool silent)\n>        {\n>        \tstruct strbuf buf = STRBUF_INIT;\n>        \tstruct lock_file lock_file = LOCK_INIT;\n>      +@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n>      + \t\tstruct object_id oid;\n>      + \n>      + \t\tstrvec_pushl(&stash.args,\n>      +-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n>      ++\t\t\t     \"stash\", \"create\",\n>      ++\t\t\t     message ? message : \"autostash\", NULL);\n>      + \t\tstash.git_cmd = 1;\n>      + \t\tstash.no_stdin = 1;\n>      + \t\tstrbuf_reset(&buf);\n>       @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n>        \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n>        \t\t}\n>      @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n>        void create_autostash(struct repository *r, const char *path)\n>        {\n>       -\tcreate_autostash_internal(r, path, NULL);\n>      -+\tcreate_autostash_internal(r, path, NULL, false);\n>      ++\tcreate_autostash_internal(r, path, NULL, NULL, false);\n>        }\n>        \n>        void create_autostash_ref(struct repository *r, const char *refname)\n>        {\n>       -\tcreate_autostash_internal(r, NULL, refname);\n>      -+\tcreate_autostash_internal(r, NULL, refname, false);\n>      ++\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n>       +}\n>       +\n>      -+void create_autostash_ref_silent(struct repository *r, const char *refname)\n>      ++void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n>      ++\t\t\t\t const char *message)\n>       +{\n>      -+\tcreate_autostash_internal(r, NULL, refname, true);\n>      ++\tcreate_autostash_internal(r, NULL, refname, message, true);\n>        }\n>        \n>        static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n>      @@ sequencer.h: void commit_post_rewrite(struct repository *r,\n>        \n>        void create_autostash(struct repository *r, const char *path);\n>        void create_autostash_ref(struct repository *r, const char *refname);\n>      -+void create_autostash_ref_silent(struct repository *r, const char *refname);\n>      ++void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n>      ++\t\t\t\t const char *message);\n>        int save_autostash(const char *path);\n>        int save_autostash_ref(struct repository *r, const char *refname);\n>        int apply_autostash(const char *path);\n>  3:  3242fd3261 ! 3:  4593745e90 sequencer: teach autostash apply to take optional conflict marker labels\n>      @@ Commit message\n>           Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n>       \n>        ## sequencer.c ##\n>      -@@ sequencer.c: void create_autostash_ref_silent(struct repository *r, const char *refname)\n>      - \tcreate_autostash_internal(r, NULL, refname, true);\n>      +@@ sequencer.c: void create_autostash_ref_with_msg_silent(struct repository *r, const char *refn\n>      + \tcreate_autostash_internal(r, NULL, refname, message, true);\n>        }\n>        \n>       -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n>       +static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n>       +\t\t\t\t    const char *label_ours, const char *label_theirs,\n>      -+\t\t\t\t    const char *label_base)\n>      ++\t\t\t\t    const char *label_base,\n>      ++\t\t\t\t    const char *stash_msg)\n>        {\n>        \tstruct child_process child = CHILD_PROCESS_INIT;\n>        \tint ret = 0;\n>      @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n>        \t\tstrvec_push(&child.args, stash_oid);\n>        \t\tret = run_command(&child);\n>        \t}\n>      +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n>      + \t\tstrvec_push(&store.args, \"stash\");\n>      + \t\tstrvec_push(&store.args, \"store\");\n>      + \t\tstrvec_push(&store.args, \"-m\");\n>      +-\t\tstrvec_push(&store.args, \"autostash\");\n>      ++\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n>      + \t\tstrvec_push(&store.args, \"-q\");\n>      + \t\tstrvec_push(&store.args, stash_oid);\n>      + \t\tif (run_command(&store))\n>       @@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n>        \t}\n>        \tstrbuf_trim(&stash_oid);\n>        \n>       -\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n>       +\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n>      -+\t\t\t\t      NULL, NULL, NULL);\n>      ++\t\t\t\t      NULL, NULL, NULL, NULL);\n>        \n>        \tunlink(path);\n>        \tstrbuf_release(&stash_oid);\n>      @@ sequencer.c: int apply_autostash(const char *path)\n>        int apply_autostash_oid(const char *stash_oid)\n>        {\n>       -\treturn apply_save_autostash_oid(stash_oid, 1);\n>      -+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n>      ++\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n>        }\n>        \n>        static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>       -\t\t\t\t    int attempt_apply)\n>       +\t\t\t\t    int attempt_apply,\n>       +\t\t\t\t    const char *label_ours, const char *label_theirs,\n>      -+\t\t\t\t    const char *label_base)\n>      ++\t\t\t\t    const char *label_base,\n>      ++\t\t\t\t    const char *stash_msg)\n>        {\n>        \tstruct object_id stash_oid;\n>        \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n>      @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n>        \toid_to_hex_r(stash_oid_hex, &stash_oid);\n>       -\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n>       +\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n>      -+\t\t\t\t       label_ours, label_theirs, label_base);\n>      ++\t\t\t\t       label_ours, label_theirs, label_base,\n>      ++\t\t\t\t       stash_msg);\n>        \n>        \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n>        \t\t\t&stash_oid, REF_NO_DEREF);\n>      @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n>        int save_autostash_ref(struct repository *r, const char *refname)\n>        {\n>       -\treturn apply_save_autostash_ref(r, refname, 0);\n>      -+\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n>      ++\treturn apply_save_autostash_ref(r, refname, 0,\n>      ++\t\t\t\t\tNULL, NULL, NULL, NULL);\n>        }\n>        \n>        int apply_autostash_ref(struct repository *r, const char *refname)\n>        {\n>       -\treturn apply_save_autostash_ref(r, refname, 1);\n>      -+\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n>      ++\treturn apply_save_autostash_ref(r, refname, 1,\n>      ++\t\t\t\t\tNULL, NULL, NULL, NULL);\n>       +}\n>       +\n>       +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n>       +\t\t\t\t    const char *label_ours, const char *label_theirs,\n>      -+\t\t\t\t    const char *label_base)\n>      ++\t\t\t\t    const char *label_base,\n>      ++\t\t\t\t    const char *stash_msg)\n>       +{\n>       +\treturn apply_save_autostash_ref(r, refname, 1,\n>      -+\t\t\t\t\tlabel_ours, label_theirs, label_base);\n>      ++\t\t\t\t\tlabel_ours, label_theirs, label_base,\n>      ++\t\t\t\t\tstash_msg);\n>        }\n>        \n>        static int checkout_onto(struct repository *r, struct replay_opts *opts,\n>      @@ sequencer.h: int save_autostash_ref(struct repository *r, const char *refname);\n>        int apply_autostash_ref(struct repository *r, const char *refname);\n>       +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n>       +\t\t\t\t    const char *label_ours, const char *label_theirs,\n>      -+\t\t\t\t    const char *label_base);\n>      ++\t\t\t\t    const char *label_base,\n>      ++\t\t\t\t    const char *stash_msg);\n>        \n>        #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n>        #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n>  4:  97a5d87c81 ! 4:  911e520431 checkout: -m (--merge) uses autostash when switching branches\n>      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>       +\t\t\tstrbuf_addf(&autostash_msg,\n>       +\t\t\t\t    \"autostash while switching to '%s'\",\n>       +\t\t\t\t    new_branch_info->name);\n>      -+\t\t\tcreate_autostash_ref_silent_with_msg(the_repository,\n>      -+\t\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n>      -+\t\t\t\t\t\t\t    autostash_msg.buf);\n>      ++\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n>      ++\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n>      ++\t\t\t\t\t\t   autostash_msg.buf);\n>       +\t\t\tcreated_autostash = 1;\n>       +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>       +\t\t}\n>      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>        }\n>       \n>        ## sequencer.c ##\n>      -@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n>      - static void create_autostash_internal(struct repository *r,\n>      - \t\t\t\t      const char *path,\n>      - \t\t\t\t      const char *refname,\n>      -+\t\t\t\t      const char *message,\n>      - \t\t\t\t      bool silent)\n>      - {\n>      - \tstruct strbuf buf = STRBUF_INIT;\n>      -@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n>      - \t\tstruct object_id oid;\n>      - \n>      - \t\tstrvec_pushl(&stash.args,\n>      --\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n>      -+\t\t\t     \"stash\", \"create\",\n>      -+\t\t\t     message ? message : \"autostash\", NULL);\n>      - \t\tstash.git_cmd = 1;\n>      - \t\tstash.no_stdin = 1;\n>      - \t\tstrbuf_reset(&buf);\n>      -@@ sequencer.c: static void create_autostash_internal(struct repository *r,\n>      - \n>      - void create_autostash(struct repository *r, const char *path)\n>      - {\n>      --\tcreate_autostash_internal(r, path, NULL, false);\n>      -+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n>      - }\n>      - \n>      - void create_autostash_ref(struct repository *r, const char *refname)\n>      - {\n>      --\tcreate_autostash_internal(r, NULL, refname, false);\n>      -+\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n>      - }\n>      - \n>      - void create_autostash_ref_silent(struct repository *r, const char *refname)\n>      - {\n>      --\tcreate_autostash_internal(r, NULL, refname, true);\n>      -+\tcreate_autostash_internal(r, NULL, refname, NULL, true);\n>      -+}\n>      -+\n>      -+void create_autostash_ref_silent_with_msg(struct repository *r,\n>      -+\t\t\t\t\t  const char *refname,\n>      -+\t\t\t\t\t  const char *message)\n>      -+{\n>      -+\tcreate_autostash_internal(r, NULL, refname, message, true);\n>      - }\n>      - \n>      - static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n>      - \t\t\t\t    const char *label_ours, const char *label_theirs,\n>      --\t\t\t\t    const char *label_base)\n>      -+\t\t\t\t    const char *label_base,\n>      -+\t\t\t\t    const char *stash_msg)\n>      - {\n>      - \tstruct child_process child = CHILD_PROCESS_INIT;\n>      - \tint ret = 0;\n>       @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n>      - \t\tstrvec_push(&store.args, \"stash\");\n>      - \t\tstrvec_push(&store.args, \"store\");\n>      - \t\tstrvec_push(&store.args, \"-m\");\n>      --\t\tstrvec_push(&store.args, \"autostash\");\n>      -+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n>      - \t\tstrvec_push(&store.args, \"-q\");\n>        \t\tstrvec_push(&store.args, stash_oid);\n>        \t\tif (run_command(&store))\n>        \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n>      @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n>        \t}\n>        \n>        \treturn ret;\n>      -@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply)\n>      - \tstrbuf_trim(&stash_oid);\n>      - \n>      - \tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n>      --\t\t\t\t      NULL, NULL, NULL);\n>      -+\t\t\t\t      NULL, NULL, NULL, NULL);\n>      - \n>      - \tunlink(path);\n>      - \tstrbuf_release(&stash_oid);\n>      -@@ sequencer.c: int apply_autostash(const char *path)\n>      - \n>      - int apply_autostash_oid(const char *stash_oid)\n>      - {\n>      --\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL);\n>      -+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n>      - }\n>      - \n>      - static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>      - \t\t\t\t    int attempt_apply,\n>      - \t\t\t\t    const char *label_ours, const char *label_theirs,\n>      --\t\t\t\t    const char *label_base)\n>      -+\t\t\t\t    const char *label_base,\n>      -+\t\t\t\t    const char *stash_msg)\n>      - {\n>      - \tstruct object_id stash_oid;\n>      - \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n>      -@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>      - \n>      - \toid_to_hex_r(stash_oid_hex, &stash_oid);\n>      - \tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n>      --\t\t\t\t       label_ours, label_theirs, label_base);\n>      -+\t\t\t\t       label_ours, label_theirs, label_base,\n>      -+\t\t\t\t       stash_msg);\n>      - \n>      - \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n>      - \t\t\t&stash_oid, REF_NO_DEREF);\n>      -@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname,\n>      - \n>      - int save_autostash_ref(struct repository *r, const char *refname)\n>      - {\n>      --\treturn apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL);\n>      -+\treturn apply_save_autostash_ref(r, refname, 0,\n>      -+\t\t\t\t\tNULL, NULL, NULL, NULL);\n>      - }\n>      - \n>      - int apply_autostash_ref(struct repository *r, const char *refname)\n>      - {\n>      --\treturn apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL);\n>      -+\treturn apply_save_autostash_ref(r, refname, 1,\n>      -+\t\t\t\t\tNULL, NULL, NULL, NULL);\n>      - }\n>      - \n>      - int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n>      - \t\t\t\t    const char *label_ours, const char *label_theirs,\n>      --\t\t\t\t    const char *label_base)\n>      -+\t\t\t\t    const char *label_base,\n>      -+\t\t\t\t    const char *stash_msg)\n>      - {\n>      - \treturn apply_save_autostash_ref(r, refname, 1,\n>      --\t\t\t\t\tlabel_ours, label_theirs, label_base);\n>      -+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n>      -+\t\t\t\t\tstash_msg);\n>      - }\n>      - \n>      - static int checkout_onto(struct repository *r, struct replay_opts *opts,\n>      -\n>      - ## sequencer.h ##\n>      -@@ sequencer.h: void commit_post_rewrite(struct repository *r,\n>      - void create_autostash(struct repository *r, const char *path);\n>      - void create_autostash_ref(struct repository *r, const char *refname);\n>      - void create_autostash_ref_silent(struct repository *r, const char *refname);\n>      -+void create_autostash_ref_silent_with_msg(struct repository *r,\n>      -+\t\t\t\t\t  const char *refname,\n>      -+\t\t\t\t\t  const char *message);\n>      - int save_autostash(const char *path);\n>      - int save_autostash_ref(struct repository *r, const char *refname);\n>      - int apply_autostash(const char *path);\n>      -@@ sequencer.h: int apply_autostash_oid(const char *stash_oid);\n>      - int apply_autostash_ref(struct repository *r, const char *refname);\n>      - int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n>      - \t\t\t\t    const char *label_ours, const char *label_theirs,\n>      --\t\t\t\t    const char *label_base);\n>      -+\t\t\t\t    const char *label_base,\n>      -+\t\t\t\t    const char *stash_msg);\n>      - \n>      - #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n>      - #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n>       \n>        ## t/t3420-rebase-autostash.sh ##\n>       @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n"},{"id":"541567","messageId":"xmqqbjflcxyx.fsf@gitster.g","threadId":"65220","inReplyTo":"d5a47638-545b-44b3-9da5-803c06b3f98a@gmail.com","subject":"Re: [PATCH v12 1/4] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T16:23:34Z","receivedAt":"2026-04-14T16:23:36Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> +static int do_apply_stash_with_labels(const char *prefix,\n>> +\t\t\t\t      struct stash_info *info,\n>> +\t\t\t\t      int index, int quiet,\n>> +\t\t\t\t      const char *label_ours, const char *label_theirs,\n>> +\t\t\t\t      const char *label_base)\n>\n> There are only four callers of do_apply_stash so it might be better just \n> to change the function signature and update the existing callers rather \n> than adding another function.\n>\n>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n>> index 70879941c2..00bcb1f802 100755\n>> --- a/t/t3903-stash.sh\n>> +++ b/t/t3903-stash.sh\n>> @@ -1666,6 +1666,35 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n>>   \t)\n>>   '\n>>   \n>> +test_expect_success 'apply with custom conflict labels' '\n>> +\tgit init conflict_labels &&\n>> +\t(\n>\n> I'm still unclear why we're creating a new repository here. Our test \n> suite is slow enough already without each test spending time creating \n> its own repository. There doesn't seem to be anything here that requires \n> isolating the test in this way.\n\nBoth are exellent points.  \n\nI also agree with your comments on create_autostash_ref() in [2/4],\nextending apply_autostash_ref() with optional three or four extra\nparameters and updating existing callers in [3/4].\n\nI have v10 already merged to 'next', but I think it is better to\nrevert the merge and give these finishing touches, as we are not\nin a rush to add more topics to 'next' before 2.54 final anyway.\n\nThanks.\n\n"},{"id":"541568","messageId":"xmqqy0ipbio0.fsf@gitster.g","threadId":"65220","inReplyTo":"f012cc7e-14fa-40d2-84dc-7407fdceb36d@gmail.com","subject":"Re: [PATCH v12 4/4] checkout: -m (--merge) uses autostash when switching branches","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T16:39:27Z","receivedAt":"2026-04-14T16:39:30Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Harald\n>\n> For the subject line I think\n>\n>      checkout -m: autostash when switching branches\n>\n> would be more in keeping with our usual style.\n\nThanks.  I agree.  Alternatively, \n\n    checkout: autostash when switching branches with -m\n\nwould also work.\n\n>> When switching branches with \"git checkout -m\", local modifications\n>> can block the switch. \n>\n> Really? Isn't the point of \"checkout -m\" to merge the local \n> modifications into the branch that's being checked out?\n\nYeah, either \"git checkout -m\" -> \"git checkout\" (without \"-m\")\nor \"can block\" -> \"can cause conflict during\".  Also, I think a bit\nmore description of what happens in the current system without this\npatch series would clarify the motivation.  Perhaps something like...\n\n    When switching branches with \"git checkout -m\", the attempted\n    merge of local modifications may cause conflicts with the\n    changes made on the other branch, which the user may not want to\n    (or may not be able to) resolve right now.  Because there is no\n    easy way to recover from this situation, we discouraged users from\n    using \"checkout -m\" unless they are certain their changes are\n    trivial and within their ability to resolve conflicts.\n\n... would contrast well with \"the user can resolve or reset and\npostpone 'stash pop' to some later time\" we will give at the end of\nthe message.\n\n>> Teach the -m flow to create a temporary stash\n>> before switching and reapply it after.  On success, only \"Applied\n>> autostash.\" is shown. \n>\n> and a diff of the local changes?\n\nI noticed that we show the same output as a successful \"git checkout\nother-branch\" without \"-m\" shows, i.e., short list of modified\npaths.  I am not sure what it is officially called but calling it\n\"diff\" is probably misleading.\n\n>> If reapplying causes conflicts, the stash is\n>> kept and the user is told they can resolve and run \"git stash drop\",\n>> or run \"git reset --hard\" and later \"git stash pop\" to recover their\n>> changes.\n\nGood.\n\n>> @@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>>   \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n>>   \n>>   \t\tif (unmerged_index(the_repository->index)) {\n>> +\t\t\trollback_lock_file(&lock_file);\n>>   \t\t\terror(_(\"you need to resolve your current index first\"));\n>>   \t\t\treturn 1;\n>\n> The changes up to here look like fixes for an existing bug and so would \n> be better in a separate patch.\n\nGood point.\n\n> Sometimes we return \"1\" and sometimes \"-1\" what does that signal to the \n> caller?\n\nThis too.\n\n>> @@ -846,82 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>>   \t\tret = unpack_trees(2, trees, &topts);\n>>   \t\tclear_unpack_trees_porcelain(&topts);\n>>   \t\tif (ret == -1) {\n>> [lots of deletions]\n>> -\t\t\tif (ret)\n>> -\t\t\t\treturn ret;\n>> +\t\t\trollback_lock_file(&lock_file);\n>> +\t\t\treturn 1;\n>>   \t\t}\n>>   \t}\n>>   \n>> @@ -1166,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n>>   \tstruct object_id rev;\n>>   \tint flag, writeout_error = 0;\n>>   \tint do_merge = 1;\n>> +\tint created_autostash = 0;\n>\n> This can be a bool\n\nSo are many things (like do_merge, writeout_error).  I wouldn't\nworry too much about the difference between int and bool unless it\nis a function parameter.\n\n>> +\t\t\tstrbuf_addf(&autostash_msg,\n>> +\t\t\t\t    \"autostash while switching to '%s'\",\n>> +\t\t\t\t    new_branch_info->name);\n>> +\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n>> +\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n>\n> It's a shame we have to create a ref here. MERGE_AUTOSTASH exists so \n> that \"git merge --continue\" can apply the stash once the user has \n> resolved any merge conflicts. We don't have that problem here because \n> there is no user interaction and we could just hold onto the stash oid \n> in a variable.\n\nHmph, so it is not a shame and we can do without it?\n"},{"id":"541575","messageId":"xmqqy0ipa169.fsf@gitster.g","threadId":"65220","inReplyTo":"xmqqmrz5eklt.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T17:42:38Z","receivedAt":"2026-04-14T17:42:40Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Harald Nordgren <haraldnordgren@gmail.com> writes:\n>\n>> Sounds reasonable, but wouldn't it make more sense to call it \"autostash\n>> from master\". We should still be able to abort the merge and merge it to\n>> some other branch. I feel like the source is more relevant than the\n>> destination, no?\n>\n> The new comment is for reminder, so \"I made this while switching\n> from 'master' to this new 'topic'\" theoretically has more reminding\n> value than \"I made this while switching to this new 'topic'\".  As I\n> outlined my workflow, I usually am on 'master' or 'next' when I end\n> up needing \"co -m\" option, so \"I was on 'master' when I stashed\n> this\" has a much weaker reminding value.  Just like a series of\n> \"autostash\" without any context comment irritated me, I'll see many\n> \"autostash on master\" that I cannot quite distinguish.\n>\n> But that may be just me.\n\nThinking about it a bit more, I doubt it would be just me.\n\nThe whole point of \"git checkout -m other-branch\" is \"oops, I\nstarted working on this thing while I am on <this> branch, but all\nof this changes are irrelevant in the context of this branch and I\nrealize that they are better done in the context of that other\nbranch\".  So as a name that reminds readers of \"git stash list\" what\nthis particular stash entry is about, the name of that other branch\nyou were switching to is much more relevant than the branch you were\non when you started working on.\n"},{"id":"541577","messageId":"20260414183503.53180-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"e651e491-e59f-462a-9e81-35546112ce57@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T18:35:03Z","receivedAt":"2026-04-14T18:35:07Z","isPatch":true,"body":"> > From: Harald Nordgren <haraldnordgren@gmail.com>\n> > \n> > Add a silent parameter to create_autostash_internal and introduce\n> > create_autostash_ref_silent so that callers can create an autostash\n> > without printing the \"Created autostash\" message.\n> > \n> > Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> I wonder if we should just update the two callers of \n> create_autostash_ref() instead of adding a new function but the \n> implementation looks sensible\n\nGood point, I will update it!\n\n\nHarald\n"},{"id":"541578","messageId":"20260414184409.82539-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"ebb65b1d-879f-4d9f-b25e-fe1c58a47ff7@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T18:44:09Z","receivedAt":"2026-04-14T18:44:12Z","isPatch":true,"body":"> > diff --git a/sequencer.h b/sequencer.h\n> > index 5d3bc83314..b0c891d3b6 100644\n> > --- a/sequencer.h\n> > +++ b/sequencer.h\n> > @@ -237,6 +237,10 @@ int save_autostash_ref(struct repository *r, const char *refname);\n> >   int apply_autostash(const char *path);\n> >   int apply_autostash_oid(const char *stash_oid);\n> >   int apply_autostash_ref(struct repository *r, const char *refname);\n> > +int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n> > +                                 const char *label_ours, const char *label_theirs,\n> > +                                 const char *label_base,\n> > +                                 const char *stash_msg);\n\nFair enough, will update in the next patch!\n\n\nHarald\n"},{"id":"541579","messageId":"20260414185640.16609-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"d5a47638-545b-44b3-9da5-803c06b3f98a@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T18:56:40Z","receivedAt":"2026-04-14T18:56:43Z","isPatch":true,"body":"> There are only four callers of do_apply_stash so it might be better just \n> to change the function signature and update the existing callers rather \n> than adding another function.\n\nAlso a good point, and I will update it.\n\nHarald\n"},{"id":"541581","messageId":"20260414200640.50910-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"f012cc7e-14fa-40d2-84dc-7407fdceb36d@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T20:06:40Z","receivedAt":"2026-04-14T20:06:44Z","isPatch":true,"body":"> The changes up to here look like fixes for an existing bug and so would \n> be better in a separate patch.\n\n👍\n\n> Sometimes we return \"1\" and sometimes \"-1\" what does that signal to the \n> caller?\n\nI just tried to follow a pattern, I'm not knowlegable of how this return code will be used. Futher down in the file we check 'ret == -1' and turn it into 1, so maybe 1 is correct?\n\n> > +                                                    autostash_msg.len ? autostash_msg.buf : NULL);\n> \n> Can we create an autostash without setting a message in autostash_msg?\n\nNo, seems not. I'll simplify it!\n\n> > +     if (created_autostash && !opts->discard_changes && !opts->quiet &&\n> \n> Wouldn't it be a bug if we've created and autostash when\n> opts->discard_changes is set? Why do we need to check it?\n\nI'll simplify it!\n\n> > +\t    new_branch_info->commit)\n> > +\t\tshow_local_changes(&new_branch_info->commit->object,\n> > +\t\t\t\t   &opts->diff_options);\n>\n> So this is a change to the output when using \"checkout -m\"? If so it \n> might be better as a separate change.\n\nDo you mean to drop if from my patchset, or just make it a separate\ncommit within this series?\n\n\nHarald\n"},{"id":"541582","messageId":"20260414200827.51072-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"d5a47638-545b-44b3-9da5-803c06b3f98a@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T20:08:27Z","receivedAt":"2026-04-14T20:08:30Z","isPatch":true,"body":"> > +test_expect_success 'apply with custom conflict labels' '\n> > +\tgit init conflict_labels &&\n> > +\t(\n> \n> I'm still unclear why we're creating a new repository here. Our test \n> suite is slow enough already without each test spending time creating \n> its own repository. There doesn't seem to be anything here that requires \n> isolating the test in this way.\n\nYes, I want this too, but I had some problems to get it to work. Found a\nway now I think, but the cleanup is not 100% trivial (this is the only\nreason to run anything inside a new repo).\n\nHarald\n"},{"id":"541584","messageId":"20260414201327.51745-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"f012cc7e-14fa-40d2-84dc-7407fdceb36d@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T20:13:27Z","receivedAt":"2026-04-14T20:13:31Z","isPatch":true,"body":"> > +                     strbuf_addf(&autostash_msg,\n> > +                                 \"autostash while switching to '%s'\",\n> > +                                 new_branch_info->name);\n> > +                     create_autostash_ref_with_msg_silent(the_repository,\n> > +                                                \"CHECKOUT_AUTOSTASH_HEAD\",\n> \n> It's a shame we have to create a ref here. MERGE_AUTOSTASH exists so\n> that \"git merge --continue\" can apply the stash once the user has\n> resolved any merge conflicts. We don't have that problem here because\n> there is no user interaction and we could just hold onto the stash oid\n> in a variable.\n\nI don't know how to actually do that. Maybe better to do later?\n\n> > +                                                autostash_msg.buf);\n> > +                     created_autostash = 1;\n> > +                     ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> > +             }\n> >               if (ret) {\n> \n> I'm confused by this - if we stash then don't we expect the call to\n> unpack_trees() in merge_working_tree() to succeed and therefore return\n> 0? If opts->merge is false then we should not be trying to apply the\n> stash when merge_working_tree() fails.\n\nSame here, I'm not sure how to get this to work. Maybe better to do later?\n\n\nHarald\n"},{"id":"541585","messageId":"20260414201636.51956-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqfr4xcz7s.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-14T20:16:36Z","receivedAt":"2026-04-14T20:16:39Z","isPatch":true,"body":">    The description of the Pull Request will be used as cover\n>    letter, ...\n>\n> so perhaps your pull-request comment should have something more than\n> just the list of CC: recipients?\n\nI'll give it a try!\n\n\nHarald\n"},{"id":"541590","messageId":"xmqq1pgh9s6o.fsf@gitster.g","threadId":"65220","inReplyTo":"20260414201636.51956-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-14T20:56:47Z","receivedAt":"2026-04-14T20:56:49Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>>    The description of the Pull Request will be used as cover\n>>    letter, ...\n>>\n>> so perhaps your pull-request comment should have something more than\n>> just the list of CC: recipients?\n>\n> I'll give it a try!\n\n;-)\n\nI find that many topics by Patrick Steinhardt and Jeff King with\nmultiple iterations often come with good cover letters that outline\nupdates between iterations.\n\nThanks.\n\n\n"},{"id":"541615","messageId":"20260415081659.86783-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"f012cc7e-14fa-40d2-84dc-7407fdceb36d@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-15T08:16:59Z","receivedAt":"2026-04-15T08:17:03Z","isPatch":true,"body":"> > +\tif (old_branch_info.name)\n> > +\t\tstash_label_base = old_branch_info.name;\n> > +\telse if (old_branch_info.commit) {\n> > +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n> > +\t\t\t\t\t &old_branch_info.commit->object.oid,\n> > +\t\t\t\t\t DEFAULT_ABBREV);\n> > +\t\tstash_label_base = old_commit_shortname.buf;\n> > +\t}\n> > +\n> >   \tif (do_merge) {\n> >   \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> > +\t\tif (ret && opts->merge) {\n> \n> As we saw above merge_working_tree() can return non-zero for a variety \n> of reasons. We only want to try stashing if the call to unpack_trees() \n> failed. Even then if you look at the list of errors in unpack-trees.h \n> you'll see that only a few of them relate to problems that can be solved \n> by stashing. The old code just tried merging whenever unpack_trees() \n> failed so it probably not so bad to do the same here but we should not \n> be stashing if merge_working_tree() returns before calling unpack_trees().\n\nWhat you are saying makes a lot of sense.\n\nI gave this a shot now, trying to return an error code that only attempts\nthe stashing when it has a chance of improving the outcome. Not at all sure\nif it's correct though!\n\n> > +\t\t\t\t\t\t   autostash_msg.buf);\n> > +\t\t\tcreated_autostash = 1;\n> > +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> > +\t\t}\n> >   \t\tif (ret) {\n> \n> I'm confused by this - if we stash then don't we expect the call to \n> unpack_trees() in merge_working_tree() to succeed and therefore return \n> 0? If opts->merge is false then we should not be trying to apply the \n> stash when merge_working_tree() fails.\n\nI'm attempting to fix this by making call to apply_autostash_ref\nconditional on whether or not the autostash was actually created. Makes\nsense?\n\n\nHarald\n"},{"id":"541616","messageId":"20260415081900.86956-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"20260414201327.51745-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-15T08:19:00Z","receivedAt":"2026-04-15T08:19:03Z","isPatch":true,"body":"> > > +                     strbuf_addf(&autostash_msg,\n> > > +                                 \"autostash while switching to '%s'\",\n> > > +                                 new_branch_info->name);\n> > > +                     create_autostash_ref_with_msg_silent(the_repository,\n> > > +                                                \"CHECKOUT_AUTOSTASH_HEAD\",\n> > \n> > It's a shame we have to create a ref here. MERGE_AUTOSTASH exists so\n> > that \"git merge --continue\" can apply the stash once the user has\n> > resolved any merge conflicts. We don't have that problem here because\n> > there is no user interaction and we could just hold onto the stash oid\n> > in a variable.\n> \n> I don't know how to actually do that. Maybe better to do later?\n\nA gave this a try, but it becomes a very big change. Or maybe I'm missing\nsome key knowledge here.\n\n> > > +                                                autostash_msg.buf);\n> > > +                     created_autostash = 1;\n> > > +                     ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> > > +             }\n> > >               if (ret) {\n> > \n> > I'm confused by this - if we stash then don't we expect the call to\n> > unpack_trees() in merge_working_tree() to succeed and therefore return\n> > 0? If opts->merge is false then we should not be trying to apply the\n> > stash when merge_working_tree() fails.\n> \n> Same here, I'm not sure how to get this to work. Maybe better to do later?\n\nI think I succeeded with this one.\n\n\nHarald\n"},{"id":"541617","messageId":"516b88c8-ebc2-4d28-9a00-4149426ad43c@gmail.com","threadId":"65220","inReplyTo":"20260415081900.86956-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-15T09:34:32Z","receivedAt":"2026-04-15T09:34:37Z","isPatch":true,"body":"On 15/04/2026 09:19, Harald Nordgren wrote:\n>>>> +                     strbuf_addf(&autostash_msg,\n>>>> +                                 \"autostash while switching to '%s'\",\n>>>> +                                 new_branch_info->name);\n>>>> +                     create_autostash_ref_with_msg_silent(the_repository,\n>>>> +                                                \"CHECKOUT_AUTOSTASH_HEAD\",\n>>>\n>>> It's a shame we have to create a ref here. MERGE_AUTOSTASH exists so\n>>> that \"git merge --continue\" can apply the stash once the user has\n>>> resolved any merge conflicts. We don't have that problem here because\n>>> there is no user interaction and we could just hold onto the stash oid\n>>> in a variable.\n>>\n>> I don't know how to actually do that. Maybe better to do later?\n> \n> A gave this a try, but it becomes a very big change. Or maybe I'm missing\n> some key knowledge here.\n\nMaybe leave that for now then\n\n>>>> +                                                autostash_msg.buf);\n>>>> +                     created_autostash = 1;\n>>>> +                     ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>>>> +             }\n>>>>                if (ret) {\n>>>\n>>> I'm confused by this - if we stash then don't we expect the call to\n>>> unpack_trees() in merge_working_tree() to succeed and therefore return\n>>> 0? \n\nIn that case we apply the stash lower down so that's fine.\n\n>>> If opts->merge is false then we should not be trying to apply the\n>>> stash when merge_working_tree() fails.\n>>\n>> Same here, I'm not sure how to get this to work. Maybe better to do later?\n> \n> I think I succeeded with this one.\n\nThis one definitely needs fixing but it should be simple to do as I \nthink it is just a logic error. We should not be trying to re-apply the \nstash unless we created it and we can check \"created_autostash\" to do that.\n\nThanks\n\nPhillip\n\n> \n> \n> Harald\n\n"},{"id":"541618","messageId":"538fc297-d09c-43c1-8240-d1680fbec4cb@gmail.com","threadId":"65220","inReplyTo":"20260414200827.51072-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-15T09:34:39Z","receivedAt":"2026-04-15T09:34:44Z","isPatch":true,"body":"On 14/04/2026 21:08, Harald Nordgren wrote:\n>>> +test_expect_success 'apply with custom conflict labels' '\n>>> +\tgit init conflict_labels &&\n>>> +\t(\n>>\n>> I'm still unclear why we're creating a new repository here. Our test\n>> suite is slow enough already without each test spending time creating\n>> its own repository. There doesn't seem to be anything here that requires\n>> isolating the test in this way.\n> \n> Yes, I want this too, but I had some problems to get it to work. Found a\n> way now I think, but the cleanup is not 100% trivial (this is the only\n> reason to run anything inside a new repo).\n\nNormally the first test would setup some commits with test_commit() that \ncreates a tag so you can just use \"git reset --hard <tag>\" to start your \ntest from a known state. Unfortunately setup_stash() does not use \ntest_commit() so there are no tags. It would be useful to fix that by \nadding a line that creates a tag so that future test authors do not face \nthe same problem.\n\nThanks\n\nPhillip\n\n"},{"id":"541619","messageId":"b7e4eec5-bd2e-4652-bbb5-f2a2d0709280@gmail.com","threadId":"65220","inReplyTo":"20260414200640.50910-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-15T09:35:13Z","receivedAt":"2026-04-15T09:35:18Z","isPatch":true,"body":"On 14/04/2026 21:06, Harald Nordgren wrote:\n>> The changes up to here look like fixes for an existing bug and so would\n>> be better in a separate patch.\n> \n> 👍\n> \n>> Sometimes we return \"1\" and sometimes \"-1\" what does that signal to the\n>> caller?\n> \n> I just tried to follow a pattern, I'm not knowlegable of how this return\n > code will be used. Futher down in the file we check 'ret == -1' and\n > turn it into 1, so maybe 1 is correct?\n\nBut you can read the code to see how it is used. Tracing the return path \nof merge_working_tree(), the return value get propagated back up to the \ntop of the call stack i.e. cmd_checkout() or cmd_switch() and used as \nthe return value there. I had wondered if we were using the value on the \nway back up the stack and doing something different based on the whether \nit was \"1\" or \"-1\" but we don't so it only affects the exit code of \"git \ncheckout\". That means returning \"1\" is sensible I think.\n\n> Do you mean to drop if from my patchset, or just make it a separate\n> commit within this series?\n\nA separate commit in this series. As \"git checkout\" without \"-m\" can \nalso carry local changes across we probably should do the same there as \nwell.\n\nThanks\n\nPhillip\n\n"},{"id":"541620","messageId":"21c205c6-687f-41b4-9f43-22e6ea6928b3@gmail.com","threadId":"65220","inReplyTo":"20260415081659.86783-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-15T09:36:32Z","receivedAt":"2026-04-15T09:36:37Z","isPatch":true,"body":"On 15/04/2026 09:16, Harald Nordgren wrote:\n>>> +\tif (old_branch_info.name)\n>>> +\t\tstash_label_base = old_branch_info.name;\n>>> +\telse if (old_branch_info.commit) {\n>>> +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n>>> +\t\t\t\t\t &old_branch_info.commit->object.oid,\n>>> +\t\t\t\t\t DEFAULT_ABBREV);\n>>> +\t\tstash_label_base = old_commit_shortname.buf;\n>>> +\t}\n>>> +\n>>>    \tif (do_merge) {\n>>>    \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>>> +\t\tif (ret && opts->merge) {\n>>\n>> As we saw above merge_working_tree() can return non-zero for a variety\n>> of reasons. We only want to try stashing if the call to unpack_trees()\n>> failed. Even then if you look at the list of errors in unpack-trees.h\n>> you'll see that only a few of them relate to problems that can be solved\n>> by stashing. The old code just tried merging whenever unpack_trees()\n>> failed so it probably not so bad to do the same here but we should not\n>> be stashing if merge_working_tree() returns before calling unpack_trees().\n> \n> What you are saying makes a lot of sense.\n> \n> I gave this a shot now, trying to return an error code that only attempts\n> the stashing when it has a chance of improving the outcome. Not at all sure\n> if it's correct though!\n\nThat sounds like the right approach\n\n>>> +\t\t\t\t\t\t   autostash_msg.buf);\n>>> +\t\t\tcreated_autostash = 1;\n>>> +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>>> +\t\t}\n>>>    \t\tif (ret) {\n>>\n>> I'm confused by this - if we stash then don't we expect the call to\n>> unpack_trees() in merge_working_tree() to succeed and therefore return\n>> 0? If opts->merge is false then we should not be trying to apply the\n>> stash when merge_working_tree() fails.\n> \n> I'm attempting to fix this by making call to apply_autostash_ref\n> conditional on whether or not the autostash was actually created. Makes\n> sense?\n\nYes, exactly\n\nThanks\n\nPhillip\n\n"},{"id":"541621","messageId":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v12.git.git.1776171585.gitgitgadget@gmail.com","subject":"[PATCH v13 0/5] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T11:11:52Z","receivedAt":"2026-04-15T11:12:00Z","isPatch":true,"body":"Handling the review comments from Phillip Wood and JCH.\n\nThe ones I struggled to fix I commented on on the mailing list instead, wuld\nbe nice to get some feedback on my proposed fixes 🤗\n\nHarald Nordgren (5):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: rollback lock on early returns in merge_working_tree\n  checkout -m: autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 149 +++++++++++------------\n builtin/commit.c                |   3 +-\n builtin/merge.c                 |  15 ++-\n builtin/stash.c                 |  28 +++--\n sequencer.c                     |  73 ++++++++---\n sequencer.h                     |   7 +-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  25 ++++\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 16 files changed, 484 insertions(+), 171 deletions(-)\n\n\nbase-commit: 9e8f4e9c04e3efa494e78b710e0c5f6cc77a0a5e\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v13\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v13\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v12:\n\n 1:  9ab5431b47 ! 1:  43bfdf2136 stash: add --label-ours, --label-theirs, --label-base for apply\n     @@ builtin/stash.c\n       \tN_(\"git stash branch <branchname> [<stash>]\")\n       #define BUILTIN_STASH_STORE_USAGE \\\n      @@ builtin/stash.c: static void unstage_changes_unless_new(struct object_id *orig_tree)\n     - \t\tdie(_(\"could not write index\"));\n       }\n       \n     --static int do_apply_stash(const char *prefix, struct stash_info *info,\n     + static int do_apply_stash(const char *prefix, struct stash_info *info,\n      -\t\t\t  int index, int quiet)\n     -+static int do_apply_stash_with_labels(const char *prefix,\n     -+\t\t\t\t      struct stash_info *info,\n     -+\t\t\t\t      int index, int quiet,\n     -+\t\t\t\t      const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t      const char *label_base)\n     ++\t\t\t  int index, int quiet,\n     ++\t\t\t  const char *label_ours, const char *label_theirs,\n     ++\t\t\t  const char *label_base)\n       {\n       \tint clean, ret;\n       \tint has_index = index;\n     @@ builtin/stash.c: static int do_apply_stash(const char *prefix, struct stash_info\n       \n       \tif (oideq(&info->b_tree, &c_tree))\n       \t\to.branch1 = \"Version stash was based on\";\n     -@@ builtin/stash.c: restore_untracked:\n     - \treturn ret;\n     - }\n     - \n     -+static int do_apply_stash(const char *prefix, struct stash_info *info,\n     -+\t\t\t  int index, int quiet)\n     -+{\n     -+\treturn do_apply_stash_with_labels(prefix, info, index, quiet,\n     -+\t\t\t\t\t  NULL, NULL, NULL);\n     -+}\n     -+\n     - static int apply_stash(int argc, const char **argv, const char *prefix,\n     - \t\t       struct repository *repo UNUSED)\n     - {\n     +@@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char *prefix,\n       \tint ret = -1;\n       \tint quiet = 0;\n       \tint index = use_index;\n     @@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char\n       \t\tgoto cleanup;\n       \n      -\tret = do_apply_stash(prefix, &info, index, quiet);\n     -+\tret = do_apply_stash_with_labels(prefix, &info, index, quiet,\n     -+\t\t\t\t\t label_ours, label_theirs, label_base);\n     ++\tret = do_apply_stash(prefix, &info, index, quiet,\n     ++\t\t\t     label_ours, label_theirs, label_base);\n       cleanup:\n       \tfree_stash_info(&info);\n       \treturn ret;\n     +@@ builtin/stash.c: static int pop_stash(int argc, const char **argv, const char *prefix,\n     + \tif (get_stash_info_assert(&info, argc, argv))\n     + \t\tgoto cleanup;\n     + \n     +-\tif ((ret = do_apply_stash(prefix, &info, index, quiet)))\n     ++\tif ((ret = do_apply_stash(prefix, &info, index, quiet,\n     ++\t\t\t\t  NULL, NULL, NULL)))\n     + \t\tprintf_ln(_(\"The stash entry is kept in case \"\n     + \t\t\t    \"you need it again.\"));\n     + \telse\n     +@@ builtin/stash.c: static int branch_stash(int argc, const char **argv, const char *prefix,\n     + \tstrvec_push(&cp.args, oid_to_hex(&info.b_commit));\n     + \tret = run_command(&cp);\n     + \tif (!ret)\n     +-\t\tret = do_apply_stash(prefix, &info, 1, 0);\n     ++\t\tret = do_apply_stash(prefix, &info, 1, 0,\n     ++\t\t\t\t     NULL, NULL, NULL);\n     + \tif (!ret && info.is_stash_ref)\n     + \t\tret = do_drop_stash(&info, 0);\n     + \n      \n       ## t/t3903-stash.sh ##\n      @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit conflicts' '\n     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n       '\n       \n      +test_expect_success 'apply with custom conflict labels' '\n     -+\tgit init conflict_labels &&\n     -+\t(\n     -+\t\tcd conflict_labels &&\n     -+\t\ttest_commit base file &&\n     -+\t\techo stashed >file &&\n     -+\t\tgit stash push -m \"stashed\" &&\n     -+\t\ttest_commit upstream file &&\n     -+\t\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n     -+\t\ttest_grep \"^<<<<<<< UP\" file &&\n     -+\t\ttest_grep \"^||||||| Stash base\" file &&\n     -+\t\ttest_grep \"^>>>>>>> STASH\" file\n     -+\t)\n     ++\ttest_when_finished \"git reset --hard && git stash drop\" &&\n     ++\tgit reset --hard &&\n     ++\ttest_commit label-base conflict-file base-content &&\n     ++\techo stashed >conflict-file &&\n     ++\tgit stash push -m \"stashed\" &&\n     ++\ttest_commit label-upstream conflict-file upstream-content &&\n     ++\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n     ++\ttest_grep \"^<<<<<<< UP\" conflict-file &&\n     ++\ttest_grep \"^||||||| Stash base\" conflict-file &&\n     ++\ttest_grep \"^>>>>>>> STASH\" conflict-file\n      +'\n      +\n      +test_expect_success 'apply with empty conflict labels' '\n     -+\tgit init empty_labels &&\n     -+\t(\n     -+\t\tcd empty_labels &&\n     -+\t\ttest_commit base file &&\n     -+\t\techo stashed >file &&\n     -+\t\tgit stash push -m \"stashed\" &&\n     -+\t\ttest_commit upstream file &&\n     -+\t\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n     -+\t\ttest_grep \"^<<<<<<<$\" file &&\n     -+\t\ttest_grep \"^>>>>>>>$\" file\n     -+\t)\n     ++\ttest_when_finished \"git reset --hard && git stash drop\" &&\n     ++\tgit reset --hard &&\n     ++\ttest_commit empty-label-base conflict-file base-content &&\n     ++\techo stashed >conflict-file &&\n     ++\tgit stash push -m \"stashed\" &&\n     ++\ttest_commit empty-label-upstream conflict-file upstream-content &&\n     ++\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n     ++\ttest_grep \"^<<<<<<<$\" conflict-file &&\n     ++\ttest_grep \"^>>>>>>>$\" conflict-file\n      +'\n      +\n       test_expect_success 'stash create reports a locked index' '\n 2:  e11a622bdf ! 2:  7f3c32f5e9 sequencer: allow create_autostash to run silently\n     @@ Commit message\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     + ## builtin/merge.c ##\n     +@@ builtin/merge.c: int cmd_merge(int argc,\n     + \t\t}\n     + \n     + \t\tif (autostash)\n     +-\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n     ++\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n     ++\t\t\t\t\t     NULL, false);\n     + \t\tif (checkout_fast_forward(the_repository,\n     + \t\t\t\t\t  &head_commit->object.oid,\n     + \t\t\t\t\t  &commit->object.oid,\n     +@@ builtin/merge.c: int cmd_merge(int argc,\n     + \t\tdie_ff_impossible();\n     + \n     + \tif (autostash)\n     +-\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n     ++\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n     ++\t\t\t\t     NULL, false);\n     + \n     + \t/* We are going to make a new commit. */\n     + \tgit_committer_info(IDENT_STRICT);\n     +\n       ## sequencer.c ##\n      @@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n       \n     @@ sequencer.c: static void create_autostash_internal(struct repository *r,\n      +\tcreate_autostash_internal(r, path, NULL, NULL, false);\n       }\n       \n     - void create_autostash_ref(struct repository *r, const char *refname)\n     +-void create_autostash_ref(struct repository *r, const char *refname)\n     ++void create_autostash_ref(struct repository *r, const char *refname,\n     ++\t\t\t  const char *message, bool silent)\n       {\n      -\tcreate_autostash_internal(r, NULL, refname);\n     -+\tcreate_autostash_internal(r, NULL, refname, NULL, false);\n     -+}\n     -+\n     -+void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n     -+\t\t\t\t const char *message)\n     -+{\n     -+\tcreate_autostash_internal(r, NULL, refname, message, true);\n     ++\tcreate_autostash_internal(r, NULL, refname, message, silent);\n       }\n       \n       static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n      \n       ## sequencer.h ##\n      @@ sequencer.h: void commit_post_rewrite(struct repository *r,\n     + \t\t\t const struct object_id *new_head);\n       \n       void create_autostash(struct repository *r, const char *path);\n     - void create_autostash_ref(struct repository *r, const char *refname);\n     -+void create_autostash_ref_with_msg_silent(struct repository *r, const char *refname,\n     -+\t\t\t\t const char *message);\n     +-void create_autostash_ref(struct repository *r, const char *refname);\n     ++void create_autostash_ref(struct repository *r, const char *refname,\n     ++\t\t\t  const char *message, bool silent);\n       int save_autostash(const char *path);\n       int save_autostash_ref(struct repository *r, const char *refname);\n       int apply_autostash(const char *path);\n 3:  4593745e90 ! 3:  b279d1dac8 sequencer: teach autostash apply to take optional conflict marker labels\n     @@ Metadata\n       ## Commit message ##\n          sequencer: teach autostash apply to take optional conflict marker labels\n      \n     -    Add label_ours, label_theirs, and label_base parameters to the autostash\n     -    apply machinery so callers can pass custom conflict marker labels\n     -    through to \"git stash apply --label-ours/--label-theirs/--label-base\".\n     -    Introduce apply_autostash_ref_with_labels() for callers that want\n     -    to pass labels.\n     +    Add label_ours, label_theirs, label_base, and stash_msg parameters to\n     +    apply_autostash_ref() and the autostash apply machinery so callers can\n     +    pass custom conflict marker labels through to\n     +    \"git stash apply --label-ours/--label-theirs/--label-base\", as well as\n     +    a custom stash message for \"git stash store -m\".\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     + ## builtin/commit.c ##\n     +@@ builtin/commit.c: int cmd_commit(int argc,\n     + \t\t\t\t     &oid, flags);\n     + \t}\n     + \n     +-\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n     ++\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n     ++\t\t\t    NULL, NULL, NULL, NULL);\n     + \n     + cleanup:\n     + \tfree_commit_extra_headers(extra);\n     +\n     + ## builtin/merge.c ##\n     +@@ builtin/merge.c: static void finish(struct commit *head_commit,\n     + \trun_hooks_l(the_repository, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n     + \n     + \tif (new_head)\n     +-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n     ++\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n     ++\t\t\t\t    NULL, NULL, NULL, NULL);\n     + \tstrbuf_release(&reflog_message);\n     + }\n     + \n     +@@ builtin/merge.c: int cmd_merge(int argc,\n     + \t\t\t\t\t  &head_commit->object.oid,\n     + \t\t\t\t\t  &commit->object.oid,\n     + \t\t\t\t\t  overwrite_ignore)) {\n     +-\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n     ++\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n     ++\t\t\t\t\t    NULL, NULL, NULL, NULL);\n     + \t\t\tret = 1;\n     + \t\t\tgoto done;\n     + \t\t}\n     +@@ builtin/merge.c: int cmd_merge(int argc,\n     + \t\telse\n     + \t\t\tfprintf(stderr, _(\"Merge with strategy %s failed.\\n\"),\n     + \t\t\t\tuse_strategies[0]->name);\n     +-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n     ++\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n     ++\t\t\t\t    NULL, NULL, NULL, NULL);\n     + \t\tret = 2;\n     + \t\tgoto done;\n     + \t} else if (best_strategy == wt_strategy)\n     +\n       ## sequencer.c ##\n     -@@ sequencer.c: void create_autostash_ref_with_msg_silent(struct repository *r, const char *refn\n     - \tcreate_autostash_internal(r, NULL, refname, message, true);\n     +@@ sequencer.c: void create_autostash_ref(struct repository *r, const char *refname,\n     + \tcreate_autostash_internal(r, NULL, refname, message, silent);\n       }\n       \n      -static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n     @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n      +\t\t\t\t\tNULL, NULL, NULL, NULL);\n       }\n       \n     - int apply_autostash_ref(struct repository *r, const char *refname)\n     +-int apply_autostash_ref(struct repository *r, const char *refname)\n     ++int apply_autostash_ref(struct repository *r, const char *refname,\n     ++\t\t\tconst char *label_ours, const char *label_theirs,\n     ++\t\t\tconst char *label_base, const char *stash_msg)\n       {\n      -\treturn apply_save_autostash_ref(r, refname, 1);\n      +\treturn apply_save_autostash_ref(r, refname, 1,\n     -+\t\t\t\t\tNULL, NULL, NULL, NULL);\n     -+}\n     -+\n     -+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     -+\t\t\t\t    const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t    const char *label_base,\n     -+\t\t\t\t    const char *stash_msg)\n     -+{\n     -+\treturn apply_save_autostash_ref(r, refname, 1,\n      +\t\t\t\t\tlabel_ours, label_theirs, label_base,\n      +\t\t\t\t\tstash_msg);\n       }\n     @@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const cha\n       static int checkout_onto(struct repository *r, struct replay_opts *opts,\n      \n       ## sequencer.h ##\n     -@@ sequencer.h: int save_autostash_ref(struct repository *r, const char *refname);\n     +@@ sequencer.h: int save_autostash(const char *path);\n     + int save_autostash_ref(struct repository *r, const char *refname);\n       int apply_autostash(const char *path);\n       int apply_autostash_oid(const char *stash_oid);\n     - int apply_autostash_ref(struct repository *r, const char *refname);\n     -+int apply_autostash_ref_with_labels(struct repository *r, const char *refname,\n     -+\t\t\t\t    const char *label_ours, const char *label_theirs,\n     -+\t\t\t\t    const char *label_base,\n     -+\t\t\t\t    const char *stash_msg);\n     +-int apply_autostash_ref(struct repository *r, const char *refname);\n     ++int apply_autostash_ref(struct repository *r, const char *refname,\n     ++\t\t\tconst char *label_ours, const char *label_theirs,\n     ++\t\t\tconst char *label_base, const char *stash_msg);\n       \n       #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n       #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n -:  ---------- > 4:  04869314ec checkout: rollback lock on early returns in merge_working_tree\n 4:  911e520431 ! 5:  4b3c6025ac checkout: -m (--merge) uses autostash when switching branches\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    checkout: -m (--merge) uses autostash when switching branches\n     +    checkout -m: autostash when switching branches\n      \n     -    When switching branches with \"git checkout -m\", local modifications\n     -    can block the switch.  Teach the -m flow to create a temporary stash\n     -    before switching and reapply it after.  On success, only \"Applied\n     -    autostash.\" is shown.  If reapplying causes conflicts, the stash is\n     -    kept and the user is told they can resolve and run \"git stash drop\",\n     -    or run \"git reset --hard\" and later \"git stash pop\" to recover their\n     -    changes.\n     +    When switching branches with \"git checkout -m\", the attempted merge\n     +    of local modifications may cause conflicts with the changes made on\n     +    the other branch, which the user may not want to (or may not be able\n     +    to) resolve right now.  Because there is no easy way to recover from\n     +    this situation, we discouraged users from using \"checkout -m\" unless\n     +    they are certain their changes are trivial and within their ability\n     +    to resolve conflicts.\n     +\n     +    Teach the -m flow to create a temporary stash before switching and\n     +    reapply it after.  On success, the stash is silently applied and\n     +    the list of locally modified paths is shown, same as a successful\n     +    \"git checkout\" without \"-m\".\n     +\n     +    If reapplying causes conflicts, the stash is kept and the user is\n     +    told they can resolve and run \"git stash drop\", or run \"git reset\n     +    --hard\" and later \"git stash pop\" to recover their changes.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ builtin/checkout.c\n       #include \"setup.h\"\n       #include \"submodule.h\"\n       #include \"symlinks.h\"\n     -@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     - \tstruct tree *new_tree;\n     - \n     - \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n     --\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n     -+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n     -+\t\trollback_lock_file(&lock_file);\n     - \t\treturn error(_(\"index file corrupt\"));\n     -+\t}\n     - \n     - \tresolve_undo_clear_index(the_repository->index);\n     - \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n     -@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     - \t} else {\n     - \t\tnew_tree = repo_get_commit_tree(the_repository,\n     - \t\t\t\t\t\tnew_branch_info->commit);\n     --\t\tif (!new_tree)\n     -+\t\tif (!new_tree) {\n     -+\t\t\trollback_lock_file(&lock_file);\n     - \t\t\treturn error(_(\"unable to read tree (%s)\"),\n     - \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n     -+\t\t}\n     - \t}\n     - \tif (opts->discard_changes) {\n     - \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n     --\t\tif (ret)\n     -+\t\tif (ret) {\n     -+\t\t\trollback_lock_file(&lock_file);\n     - \t\t\treturn ret;\n     -+\t\t}\n     - \t} else {\n     - \t\tstruct tree_desc trees[2];\n     - \t\tstruct tree *tree;\n     -@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     - \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n     - \n     - \t\tif (unmerged_index(the_repository->index)) {\n     -+\t\t\trollback_lock_file(&lock_file);\n     - \t\t\terror(_(\"you need to resolve your current index first\"));\n     - \t\t\treturn 1;\n     - \t\t}\n      @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n       \t\tret = unpack_trees(2, trees, &topts);\n       \t\tclear_unpack_trees_porcelain(&topts);\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      -\t\t\tstruct strbuf sb = STRBUF_INIT;\n      -\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n      -\n     --\t\t\tif (!opts->merge)\n     +-\t\t\tif (!opts->merge) {\n     +-\t\t\t\trollback_lock_file(&lock_file);\n      -\t\t\t\treturn 1;\n     +-\t\t\t}\n      -\n      -\t\t\t/*\n      -\t\t\t * Without old_branch_info->commit, the below is the same as\n      -\t\t\t * the two-tree unpack we already tried and failed.\n      -\t\t\t */\n     --\t\t\tif (!old_branch_info->commit)\n     +-\t\t\tif (!old_branch_info->commit) {\n     +-\t\t\t\trollback_lock_file(&lock_file);\n      -\t\t\t\treturn 1;\n     +-\t\t\t}\n      -\t\t\told_tree = repo_get_commit_tree(the_repository,\n      -\t\t\t\t\t\t\told_branch_info->commit);\n      -\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      -\t\t\tret = reset_tree(new_tree,\n      -\t\t\t\t\t opts, 1,\n      -\t\t\t\t\t writeout_error, new_branch_info);\n     --\t\t\tif (ret)\n     +-\t\t\tif (ret) {\n     +-\t\t\t\trollback_lock_file(&lock_file);\n      -\t\t\t\treturn ret;\n     +-\t\t\t}\n      -\t\t\to.ancestor = old_branch_info->name;\n      -\t\t\tif (!old_branch_info->name) {\n      -\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      -\t\t\t\t\t writeout_error, new_branch_info);\n      -\t\t\tstrbuf_release(&o.obuf);\n      -\t\t\tstrbuf_release(&old_commit_shortname);\n     --\t\t\tif (ret)\n     +-\t\t\tif (ret) {\n     +-\t\t\t\trollback_lock_file(&lock_file);\n      -\t\t\t\treturn ret;\n     +-\t\t\t}\n      +\t\t\trollback_lock_file(&lock_file);\n     -+\t\t\treturn 1;\n     ++\t\t\treturn ret;\n       \t\t}\n       \t}\n       \n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\n       \tif (do_merge) {\n       \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n     -+\t\tif (ret && opts->merge) {\n     ++\t\tif (ret == -1 && opts->merge) {\n      +\t\t\tstrbuf_addf(&autostash_msg,\n      +\t\t\t\t    \"autostash while switching to '%s'\",\n      +\t\t\t\t    new_branch_info->name);\n     -+\t\t\tcreate_autostash_ref_with_msg_silent(the_repository,\n     -+\t\t\t\t\t\t   \"CHECKOUT_AUTOSTASH_HEAD\",\n     -+\t\t\t\t\t\t   autostash_msg.buf);\n     ++\t\t\tcreate_autostash_ref(the_repository,\n     ++\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n     ++\t\t\t\t\t     autostash_msg.buf, true);\n      +\t\t\tcreated_autostash = 1;\n      +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\t}\n       \t\tif (ret) {\n     -+\t\t\tapply_autostash_ref_with_labels(the_repository,\n     -+\t\t\t\t\t\t       \"CHECKOUT_AUTOSTASH_HEAD\",\n     -+\t\t\t\t\t\t       new_branch_info->name,\n     -+\t\t\t\t\t\t       \"local\",\n     -+\t\t\t\t\t\t       stash_label_base,\n     -+\t\t\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n     ++\t\t\tif (created_autostash)\n     ++\t\t\t\tapply_autostash_ref(the_repository,\n     ++\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n     ++\t\t\t\t\t\t    new_branch_info->name,\n     ++\t\t\t\t\t\t    \"local\",\n     ++\t\t\t\t\t\t    stash_label_base,\n     ++\t\t\t\t\t\t    autostash_msg.buf);\n       \t\t\tbranch_info_release(&old_branch_info);\n     +-\t\t\treturn ret;\n      +\t\t\tstrbuf_release(&old_commit_shortname);\n      +\t\t\tstrbuf_release(&autostash_msg);\n     - \t\t\treturn ret;\n     ++\t\t\treturn ret < 0 ? 1 : ret;\n       \t\t}\n       \t}\n     + \n      @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \n       \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\tgit_config_push_parameter(cfg.buf);\n      +\t\tstrbuf_release(&cfg);\n      +\t}\n     -+\tapply_autostash_ref_with_labels(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n     -+\t\t\t\t       new_branch_info->name, \"local\",\n     -+\t\t\t\t       stash_label_base,\n     -+\t\t\t\t       autostash_msg.len ? autostash_msg.buf : NULL);\n     ++\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n     ++\t\t\t    new_branch_info->name, \"local\",\n     ++\t\t\t    stash_label_base,\n     ++\t\t\t    autostash_msg.buf);\n      +\n      +\tdiscard_index(the_repository->index);\n      +\tif (repo_read_index(the_repository) < 0)\n      +\t\tdie(_(\"index file corrupt\"));\n      +\n     -+\tif (created_autostash && !opts->discard_changes && !opts->quiet &&\n     -+\t    new_branch_info->commit)\n     ++\tif (created_autostash && !opts->quiet && new_branch_info->commit)\n      +\t\tshow_local_changes(&new_branch_info->commit->object,\n      +\t\t\t\t   &opts->diff_options);\n      +\n\n-- \ngitgitgadget\n"},{"id":"541622","messageId":"43bfdf2136f648235773cd3917b6a9de968531bd.1776251517.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","subject":"[PATCH v13 1/5] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T11:11:53Z","receivedAt":"2026-04-15T11:12:01Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 28 ++++++++++++++++++++--------\n t/t3903-stash.sh             | 25 +++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 58 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..32dbc97b47 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -591,7 +591,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n }\n \n static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+\t\t\t  int index, int quiet,\n+\t\t\t  const char *label_ours, const char *label_theirs,\n+\t\t\t  const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +645,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t     label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\n@@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info_assert(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tif ((ret = do_apply_stash(prefix, &info, index, quiet)))\n+\tif ((ret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t\t  NULL, NULL, NULL)))\n \t\tprintf_ln(_(\"The stash entry is kept in case \"\n \t\t\t    \"you need it again.\"));\n \telse\n@@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,\n \tstrvec_push(&cp.args, oid_to_hex(&info.b_commit));\n \tret = run_command(&cp);\n \tif (!ret)\n-\t\tret = do_apply_stash(prefix, &info, 1, 0);\n+\t\tret = do_apply_stash(prefix, &info, 1, 0,\n+\t\t\t\t     NULL, NULL, NULL);\n \tif (!ret && info.is_stash_ref)\n \t\tret = do_drop_stash(&info, 0);\n \ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..340854bc0a 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -1666,6 +1666,31 @@ test_expect_success 'restore untracked files even when we hit conflicts' '\n \t)\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\ttest_when_finished \"git reset --hard && git stash drop\" &&\n+\tgit reset --hard &&\n+\ttest_commit label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\ttest_grep \"^<<<<<<< UP\" conflict-file &&\n+\ttest_grep \"^||||||| Stash base\" conflict-file &&\n+\ttest_grep \"^>>>>>>> STASH\" conflict-file\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\ttest_when_finished \"git reset --hard && git stash drop\" &&\n+\tgit reset --hard &&\n+\ttest_commit empty-label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit empty-label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\ttest_grep \"^<<<<<<<$\" conflict-file &&\n+\ttest_grep \"^>>>>>>>$\" conflict-file\n+'\n+\n test_expect_success 'stash create reports a locked index' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541623","messageId":"7f3c32f5e99bfbd9c3bd69db962b1e0197b0b9f2.1776251517.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","subject":"[PATCH v13 2/5] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T11:11:54Z","receivedAt":"2026-04-15T11:12:03Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/merge.c |  6 ++++--\n sequencer.c     | 17 +++++++++++------\n sequencer.h     |  3 ++-\n 3 files changed, 17 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 2cbce56f8d..3ebe190ef1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1672,7 +1672,8 @@ int cmd_merge(int argc,\n \t\t}\n \n \t\tif (autostash)\n-\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t     NULL, false);\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1764,7 +1765,8 @@ int cmd_merge(int argc,\n \t\tdie_ff_impossible();\n \n \tif (autostash)\n-\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t     NULL, false);\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..ff5258f481 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      const char *message,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n+\t\t\t     \"stash\", \"create\",\n+\t\t\t     message ? message : \"autostash\", NULL);\n \t\tstash.git_cmd = 1;\n \t\tstash.no_stdin = 1;\n \t\tstrbuf_reset(&buf);\n@@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4718,13 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n }\n \n-void create_autostash_ref(struct repository *r, const char *refname)\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..02d2d9db06 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -229,7 +229,8 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct object_id *new_head);\n \n void create_autostash(struct repository *r, const char *path);\n-void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541624","messageId":"b279d1dac8f61cfd60b09cee7996f9b17c11cd3d.1776251517.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","subject":"[PATCH v13 3/5] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T11:11:55Z","receivedAt":"2026-04-15T11:12:03Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, label_base, and stash_msg parameters to\napply_autostash_ref() and the autostash apply machinery so callers can\npass custom conflict marker labels through to\n\"git stash apply --label-ours/--label-theirs/--label-base\", as well as\na custom stash message for \"git stash store -m\".\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/commit.c |  3 ++-\n builtin/merge.c  |  9 ++++++---\n sequencer.c      | 38 +++++++++++++++++++++++++++++---------\n sequencer.h      |  4 +++-\n 4 files changed, 40 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex a3e52ac9ca..28f6174503 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1979,7 +1979,8 @@ int cmd_commit(int argc,\n \t\t\t\t     &oid, flags);\n \t}\n \n-\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t    NULL, NULL, NULL, NULL);\n \n cleanup:\n \tfree_commit_extra_headers(extra);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 3ebe190ef1..aacf8c524e 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -537,7 +537,8 @@ static void finish(struct commit *head_commit,\n \trun_hooks_l(the_repository, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tif (new_head)\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \tstrbuf_release(&reflog_message);\n }\n \n@@ -1678,7 +1679,8 @@ int cmd_merge(int argc,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n \t\t\t\t\t  overwrite_ignore)) {\n-\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\t\tret = 1;\n \t\t\tgoto done;\n \t\t}\n@@ -1851,7 +1853,8 @@ int cmd_merge(int argc,\n \t\telse\n \t\t\tfprintf(stderr, _(\"Merge with strategy %s failed.\\n\"),\n \t\t\t\tuse_strategies[0]->name);\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\tret = 2;\n \t\tgoto done;\n \t} else if (best_strategy == wt_strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex ff5258f481..7c0376d9e4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4727,7 +4727,10 @@ void create_autostash_ref(struct repository *r, const char *refname,\n \tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4738,6 +4741,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4751,7 +4760,7 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tstrvec_push(&store.args, \"stash\");\n \t\tstrvec_push(&store.args, \"store\");\n \t\tstrvec_push(&store.args, \"-m\");\n-\t\tstrvec_push(&store.args, \"autostash\");\n+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n \t\tstrvec_push(&store.args, \"-q\");\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n@@ -4782,7 +4791,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4801,11 +4811,14 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4821,7 +4834,9 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base,\n+\t\t\t\t       stash_msg);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4831,12 +4846,17 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n-int apply_autostash_ref(struct repository *r, const char *refname)\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n+\t\t\t\t\tstash_msg);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 02d2d9db06..3164bd437d 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -235,7 +235,9 @@ int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541625","messageId":"04869314ecd16c63c5067d23783a6054e8d63bf0.1776251517.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","subject":"[PATCH v13 4/5] checkout: rollback lock on early returns in merge_working_tree","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T11:11:56Z","receivedAt":"2026-04-15T11:12:05Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nmerge_working_tree() acquires the index lock via\nrepo_hold_locked_index() but several early return paths exit\nwithout calling rollback_lock_file(), leaving the lock held.\nWhile this is currently harmless because the process exits soon\nafter, it becomes a problem if the function is ever called more\nthan once in the same process.\n\nAdd rollback_lock_file() calls to all early return paths.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/checkout.c | 29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..c80c62b37b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -857,15 +864,19 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n \t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n \n-\t\t\tif (!opts->merge)\n+\t\t\tif (!opts->merge) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \n \t\t\t/*\n \t\t\t * Without old_branch_info->commit, the below is the same as\n \t\t\t * the two-tree unpack we already tried and failed.\n \t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n+\t\t\tif (!old_branch_info->commit) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \t\t\told_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\t\told_branch_info->commit);\n \n@@ -897,8 +908,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\n \t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t\to.ancestor = old_branch_info->name;\n \t\t\tif (!old_branch_info->name) {\n \t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n@@ -920,8 +933,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t writeout_error, new_branch_info);\n \t\t\tstrbuf_release(&o.obuf);\n \t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t}\n \t}\n \n-- \ngitgitgadget\n\n"},{"id":"541626","messageId":"4b3c6025ac4fdf8731f7c7894b68044adf961eda.1776251517.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","subject":"[PATCH v13 5/5] checkout -m: autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T11:11:57Z","receivedAt":"2026-04-15T11:12:06Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", the attempted merge\nof local modifications may cause conflicts with the changes made on\nthe other branch, which the user may not want to (or may not be able\nto) resolve right now.  Because there is no easy way to recover from\nthis situation, we discouraged users from using \"checkout -m\" unless\nthey are certain their changes are trivial and within their ability\nto resolve conflicts.\n\nTeach the -m flow to create a temporary stash before switching and\nreapply it after.  On success, the stash is silently applied and\nthe list of locally modified paths is shown, same as a successful\n\"git checkout\" without \"-m\".\n\nIf reapplying causes conflicts, the stash is kept and the user is\ntold they can resolve and run \"git stash drop\", or run \"git reset\n--hard\" and later \"git stash pop\" to recover their changes.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 144 +++++++++-------------\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 9 files changed, 359 insertions(+), 141 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c80c62b37b..55c4db04c6 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -853,90 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn ret;\n \t\t}\n \t}\n \n@@ -1181,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tstruct strbuf autostash_msg = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1218,11 +1140,39 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_base = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret == -1 && opts->merge) {\n+\t\t\tstrbuf_addf(&autostash_msg,\n+\t\t\t\t    \"autostash while switching to '%s'\",\n+\t\t\t\t    new_branch_info->name);\n+\t\t\tcreate_autostash_ref(the_repository,\n+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t     autostash_msg.buf, true);\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tif (created_autostash)\n+\t\t\t\tapply_autostash_ref(the_repository,\n+\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t    new_branch_info->name,\n+\t\t\t\t\t\t    \"local\",\n+\t\t\t\t\t\t    stash_label_base,\n+\t\t\t\t\t\t    autostash_msg.buf);\n \t\t\tbranch_info_release(&old_branch_info);\n-\t\t\treturn ret;\n+\t\t\tstrbuf_release(&old_commit_shortname);\n+\t\t\tstrbuf_release(&autostash_msg);\n+\t\t\treturn ret < 0 ? 1 : ret;\n \t\t}\n \t}\n \n@@ -1231,8 +1181,30 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t    new_branch_info->name, \"local\",\n+\t\t\t    stash_label_base,\n+\t\t\t    autostash_msg.buf);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->quiet && new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n+\tstrbuf_release(&autostash_msg);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 7c0376d9e4..480e8e6c0b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4765,15 +4765,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..c474c6759f 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m autostash message includes target branch' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\tgit stash list >stash-list &&\n+\ttest_grep \"autostash while switching to .side.\" stash-list &&\n+\tgit stash drop &&\n+\tgit checkout -f main &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541671","messageId":"20260415153413.1176-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"538fc297-d09c-43c1-8240-d1680fbec4cb@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-15T15:34:13Z","receivedAt":"2026-04-15T15:34:17Z","isPatch":true,"body":"> Normally the first test would setup some commits with test_commit() that\n> creates a tag so you can just use \"git reset --hard <tag>\" to start your\n> test from a known state. Unfortunately setup_stash() does not use\n> test_commit() so there are no tags. It would be useful to fix that by\n> adding a line that creates a tag so that future test authors do not face\n> the same problem.\n\nSounds reasonable, but it's surprisingly easy to break the subsequent\ntests.\n\nMy solution now will be to move these tests to last in the test file.\n\n\nHarald\n"},{"id":"541674","messageId":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v13.git.git.1776251517.gitgitgadget@gmail.com","subject":"[PATCH v14 0/5] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T16:24:14Z","receivedAt":"2026-04-15T16:24:22Z","isPatch":true,"body":"Simplifying the tests in t/t3903-stash.sh according to Phillip Wood's\ncomment. I believe everything sound be fixed now or responded to.\n\nAlso rebasing against upstream.\n\nHarald Nordgren (5):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: rollback lock on early returns in merge_working_tree\n  checkout -m: autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-stash.adoc    |  11 +-\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 149 +++++++++++------------\n builtin/commit.c                |   3 +-\n builtin/merge.c                 |  15 ++-\n builtin/stash.c                 |  28 +++--\n sequencer.c                     |  73 ++++++++---\n sequencer.h                     |   7 +-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t3903-stash.sh                |  24 ++++\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 16 files changed, 483 insertions(+), 171 deletions(-)\n\n\nbase-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v14\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v14\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v13:\n\n 1:  43bfdf2136 ! 1:  e18c25599a stash: add --label-ours, --label-theirs, --label-base for apply\n     @@ builtin/stash.c: static int branch_stash(int argc, const char **argv, const char\n       \n      \n       ## t/t3903-stash.sh ##\n     -@@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit conflicts' '\n     - \t)\n     +@@ t/t3903-stash.sh: setup_stash() {\n     + \tgit add other-file &&\n     + \ttest_tick &&\n     + \tgit commit -m initial &&\n     ++\tgit tag initial &&\n     + \techo 2 >file &&\n     + \tgit add file &&\n     + \techo 3 >file &&\n     +@@ t/t3903-stash.sh: test_expect_success 'stash.index=false overridden by --index' '\n     + \ttest_cmp expect file\n       '\n       \n      +test_expect_success 'apply with custom conflict labels' '\n     -+\ttest_when_finished \"git reset --hard && git stash drop\" &&\n     -+\tgit reset --hard &&\n     ++\tgit reset --hard initial &&\n      +\ttest_commit label-base conflict-file base-content &&\n      +\techo stashed >conflict-file &&\n      +\tgit stash push -m \"stashed\" &&\n     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n      +'\n      +\n      +test_expect_success 'apply with empty conflict labels' '\n     -+\ttest_when_finished \"git reset --hard && git stash drop\" &&\n     -+\tgit reset --hard &&\n     ++\tgit reset --hard initial &&\n      +\ttest_commit empty-label-base conflict-file base-content &&\n      +\techo stashed >conflict-file &&\n      +\tgit stash push -m \"stashed\" &&\n     @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n      +\ttest_grep \"^>>>>>>>$\" conflict-file\n      +'\n      +\n     - test_expect_success 'stash create reports a locked index' '\n     - \ttest_when_finished \"rm -rf repo\" &&\n     - \tgit init repo &&\n     + test_done\n      \n       ## xdiff/xmerge.c ##\n      @@ xdiff/xmerge.c: static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n 2:  7f3c32f5e9 = 2:  ce29b10264 sequencer: allow create_autostash to run silently\n 3:  b279d1dac8 = 3:  73051d1762 sequencer: teach autostash apply to take optional conflict marker labels\n 4:  04869314ec = 4:  191058d8e3 checkout: rollback lock on early returns in merge_working_tree\n 5:  4b3c6025ac = 5:  86f33df1eb checkout -m: autostash when switching branches\n\n-- \ngitgitgadget\n"},{"id":"541675","messageId":"e18c25599add8a4efe994419b8e9101e47a24025.1776270259.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"[PATCH v14 1/5] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T16:24:15Z","receivedAt":"2026-04-15T16:24:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 28 ++++++++++++++++++++--------\n t/t3903-stash.sh             | 24 ++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 57 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..32dbc97b47 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -591,7 +591,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n }\n \n static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+\t\t\t  int index, int quiet,\n+\t\t\t  const char *label_ours, const char *label_theirs,\n+\t\t\t  const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +645,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t     label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\n@@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info_assert(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tif ((ret = do_apply_stash(prefix, &info, index, quiet)))\n+\tif ((ret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t\t  NULL, NULL, NULL)))\n \t\tprintf_ln(_(\"The stash entry is kept in case \"\n \t\t\t    \"you need it again.\"));\n \telse\n@@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,\n \tstrvec_push(&cp.args, oid_to_hex(&info.b_commit));\n \tret = run_command(&cp);\n \tif (!ret)\n-\t\tret = do_apply_stash(prefix, &info, 1, 0);\n+\t\tret = do_apply_stash(prefix, &info, 1, 0,\n+\t\t\t\t     NULL, NULL, NULL);\n \tif (!ret && info.is_stash_ref)\n \t\tret = do_drop_stash(&info, 0);\n \ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..bdaad22e1f 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -56,6 +56,7 @@ setup_stash() {\n \tgit add other-file &&\n \ttest_tick &&\n \tgit commit -m initial &&\n+\tgit tag initial &&\n \techo 2 >file &&\n \tgit add file &&\n \techo 3 >file &&\n@@ -1790,4 +1791,27 @@ test_expect_success 'stash.index=false overridden by --index' '\n \ttest_cmp expect file\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit reset --hard initial &&\n+\ttest_commit label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\ttest_grep \"^<<<<<<< UP\" conflict-file &&\n+\ttest_grep \"^||||||| Stash base\" conflict-file &&\n+\ttest_grep \"^>>>>>>> STASH\" conflict-file\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit reset --hard initial &&\n+\ttest_commit empty-label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit empty-label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\ttest_grep \"^<<<<<<<$\" conflict-file &&\n+\ttest_grep \"^>>>>>>>$\" conflict-file\n+'\n+\n test_done\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"541676","messageId":"ce29b10264eff142370a66a67f681f490ff07934.1776270259.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"[PATCH v14 2/5] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T16:24:16Z","receivedAt":"2026-04-15T16:24:25Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/merge.c |  6 ++++--\n sequencer.c     | 17 +++++++++++------\n sequencer.h     |  3 ++-\n 3 files changed, 17 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 2cbce56f8d..3ebe190ef1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1672,7 +1672,8 @@ int cmd_merge(int argc,\n \t\t}\n \n \t\tif (autostash)\n-\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t     NULL, false);\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1764,7 +1765,8 @@ int cmd_merge(int argc,\n \t\tdie_ff_impossible();\n \n \tif (autostash)\n-\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t     NULL, false);\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..ff5258f481 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      const char *message,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n+\t\t\t     \"stash\", \"create\",\n+\t\t\t     message ? message : \"autostash\", NULL);\n \t\tstash.git_cmd = 1;\n \t\tstash.no_stdin = 1;\n \t\tstrbuf_reset(&buf);\n@@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4718,13 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n }\n \n-void create_autostash_ref(struct repository *r, const char *refname)\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..02d2d9db06 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -229,7 +229,8 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct object_id *new_head);\n \n void create_autostash(struct repository *r, const char *path);\n-void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"541677","messageId":"73051d1762b2a76ec59adb9a40ba344f808e5b66.1776270259.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"[PATCH v14 3/5] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T16:24:17Z","receivedAt":"2026-04-15T16:24:26Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, label_base, and stash_msg parameters to\napply_autostash_ref() and the autostash apply machinery so callers can\npass custom conflict marker labels through to\n\"git stash apply --label-ours/--label-theirs/--label-base\", as well as\na custom stash message for \"git stash store -m\".\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/commit.c |  3 ++-\n builtin/merge.c  |  9 ++++++---\n sequencer.c      | 38 +++++++++++++++++++++++++++++---------\n sequencer.h      |  4 +++-\n 4 files changed, 40 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex a3e52ac9ca..28f6174503 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1979,7 +1979,8 @@ int cmd_commit(int argc,\n \t\t\t\t     &oid, flags);\n \t}\n \n-\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t    NULL, NULL, NULL, NULL);\n \n cleanup:\n \tfree_commit_extra_headers(extra);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 3ebe190ef1..aacf8c524e 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -537,7 +537,8 @@ static void finish(struct commit *head_commit,\n \trun_hooks_l(the_repository, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tif (new_head)\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \tstrbuf_release(&reflog_message);\n }\n \n@@ -1678,7 +1679,8 @@ int cmd_merge(int argc,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n \t\t\t\t\t  overwrite_ignore)) {\n-\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\t\tret = 1;\n \t\t\tgoto done;\n \t\t}\n@@ -1851,7 +1853,8 @@ int cmd_merge(int argc,\n \t\telse\n \t\t\tfprintf(stderr, _(\"Merge with strategy %s failed.\\n\"),\n \t\t\t\tuse_strategies[0]->name);\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\tret = 2;\n \t\tgoto done;\n \t} else if (best_strategy == wt_strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex ff5258f481..7c0376d9e4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4727,7 +4727,10 @@ void create_autostash_ref(struct repository *r, const char *refname,\n \tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4738,6 +4741,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4751,7 +4760,7 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tstrvec_push(&store.args, \"stash\");\n \t\tstrvec_push(&store.args, \"store\");\n \t\tstrvec_push(&store.args, \"-m\");\n-\t\tstrvec_push(&store.args, \"autostash\");\n+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n \t\tstrvec_push(&store.args, \"-q\");\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n@@ -4782,7 +4791,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4801,11 +4811,14 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4821,7 +4834,9 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base,\n+\t\t\t\t       stash_msg);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4831,12 +4846,17 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n-int apply_autostash_ref(struct repository *r, const char *refname)\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n+\t\t\t\t\tstash_msg);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 02d2d9db06..3164bd437d 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -235,7 +235,9 @@ int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"541678","messageId":"191058d8e3f6794261215653aeecc4cada423171.1776270259.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"[PATCH v14 4/5] checkout: rollback lock on early returns in merge_working_tree","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T16:24:18Z","receivedAt":"2026-04-15T16:24:28Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nmerge_working_tree() acquires the index lock via\nrepo_hold_locked_index() but several early return paths exit\nwithout calling rollback_lock_file(), leaving the lock held.\nWhile this is currently harmless because the process exits soon\nafter, it becomes a problem if the function is ever called more\nthan once in the same process.\n\nAdd rollback_lock_file() calls to all early return paths.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/checkout.c | 29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..c80c62b37b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -857,15 +864,19 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n \t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n \n-\t\t\tif (!opts->merge)\n+\t\t\tif (!opts->merge) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \n \t\t\t/*\n \t\t\t * Without old_branch_info->commit, the below is the same as\n \t\t\t * the two-tree unpack we already tried and failed.\n \t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n+\t\t\tif (!old_branch_info->commit) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \t\t\told_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\t\told_branch_info->commit);\n \n@@ -897,8 +908,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\n \t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t\to.ancestor = old_branch_info->name;\n \t\t\tif (!old_branch_info->name) {\n \t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n@@ -920,8 +933,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t writeout_error, new_branch_info);\n \t\t\tstrbuf_release(&o.obuf);\n \t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t}\n \t}\n \n-- \ngitgitgadget\n\n"},{"id":"541679","messageId":"86f33df1eb043d92cc626092d512aec670c89bdb.1776270259.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"[PATCH v14 5/5] checkout -m: autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-15T16:24:19Z","receivedAt":"2026-04-15T16:24:30Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", the attempted merge\nof local modifications may cause conflicts with the changes made on\nthe other branch, which the user may not want to (or may not be able\nto) resolve right now.  Because there is no easy way to recover from\nthis situation, we discouraged users from using \"checkout -m\" unless\nthey are certain their changes are trivial and within their ability\nto resolve conflicts.\n\nTeach the -m flow to create a temporary stash before switching and\nreapply it after.  On success, the stash is silently applied and\nthe list of locally modified paths is shown, same as a successful\n\"git checkout\" without \"-m\".\n\nIf reapplying causes conflicts, the stash is kept and the user is\ntold they can resolve and run \"git stash drop\", or run \"git reset\n--hard\" and later \"git stash pop\" to recover their changes.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 ++++-----\n Documentation/git-switch.adoc   |  33 ++---\n builtin/checkout.c              | 144 +++++++++-------------\n sequencer.c                     |  18 ++-\n t/t3420-rebase-autostash.sh     |  24 +++-\n t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n t/t7600-merge.sh                |   2 +-\n xdiff-interface.c               |  12 ++\n xdiff-interface.h               |   1 +\n 9 files changed, 359 insertions(+), 141 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c80c62b37b..55c4db04c6 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -853,90 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn ret;\n \t\t}\n \t}\n \n@@ -1181,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tstruct strbuf autostash_msg = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1218,11 +1140,39 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name)\n+\t\tstash_label_base = old_branch_info.name;\n+\telse if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tif (ret == -1 && opts->merge) {\n+\t\t\tstrbuf_addf(&autostash_msg,\n+\t\t\t\t    \"autostash while switching to '%s'\",\n+\t\t\t\t    new_branch_info->name);\n+\t\t\tcreate_autostash_ref(the_repository,\n+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t     autostash_msg.buf, true);\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\t}\n \t\tif (ret) {\n+\t\t\tif (created_autostash)\n+\t\t\t\tapply_autostash_ref(the_repository,\n+\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t\t    new_branch_info->name,\n+\t\t\t\t\t\t    \"local\",\n+\t\t\t\t\t\t    stash_label_base,\n+\t\t\t\t\t\t    autostash_msg.buf);\n \t\t\tbranch_info_release(&old_branch_info);\n-\t\t\treturn ret;\n+\t\t\tstrbuf_release(&old_commit_shortname);\n+\t\t\tstrbuf_release(&autostash_msg);\n+\t\t\treturn ret < 0 ? 1 : ret;\n \t\t}\n \t}\n \n@@ -1231,8 +1181,30 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (opts->conflict_style >= 0) {\n+\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\tgit_config_push_parameter(cfg.buf);\n+\t\tstrbuf_release(&cfg);\n+\t}\n+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t    new_branch_info->name, \"local\",\n+\t\t\t    stash_label_base,\n+\t\t\t    autostash_msg.buf);\n+\n+\tdiscard_index(the_repository->index);\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(_(\"index file corrupt\"));\n+\n+\tif (created_autostash && !opts->quiet && new_branch_info->commit)\n+\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t   &opts->diff_options);\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n+\tstrbuf_release(&autostash_msg);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 7c0376d9e4..480e8e6c0b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4765,15 +4765,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n+\t\t\t\t  \"\\n\"\n+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n+\t\t\t\t  \"   recover your local changes.\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..e4e2cb19ce 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,30 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however, applying it to carry\n+\tforward your local changes resulted in conflicts:\n+\n+\t - You can try resolving them now.  If you resolved them\n+\t   successfully, discard the stash entry with \"git stash drop\".\n+\n+\t - Alternatively you can \"git reset --hard\" if you do not want\n+\t   to deal with them right now, and later \"git stash pop\" to\n+\t   recover your local changes.\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..c474c6759f 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tfill a b X d e >two &&\n+\tgit checkout --merge --conflict=zdiff3 simple &&\n+\n+\tcat <<-EOF >expect &&\n+\ta\n+\t<<<<<<< simple\n+\tc\n+\t||||||| main\n+\tb\n+\tc\n+\td\n+\t=======\n+\tb\n+\tX\n+\td\n+\t>>>>>>> local\n+\te\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\ttest_config merge.conflictStyle diff3 &&\n+\tfill b d >two &&\n+\tgit checkout -m simple &&\n+\n+\tcat <<-EOF >expect &&\n+\t<<<<<<< simple\n+\ta\n+\tc\n+\te\n+\t||||||| main\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\t=======\n+\tb\n+\td\n+\t>>>>>>> local\n+\tEOF\n+\ttest_cmp expect two\n+'\n+\n+test_expect_success 'checkout -m skips stash when no conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit stash list >stash-before &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same\n+'\n+\n+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m produces usable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"recover your local changes\" actual &&\n+\tgit checkout -f main &&\n+\tgit stash pop &&\n+\tfill 1 2 3 4 5 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m autostash message includes target branch' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\tgit stash list >stash-list &&\n+\ttest_grep \"autostash while switching to .side.\" stash-list &&\n+\tgit stash drop &&\n+\tgit checkout -f main &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\tgit add one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n+\tgit checkout -f main &&\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b nonoverlap_base &&\n+\tfill a b c d >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b nonoverlap_child &&\n+\tfill a b c INSERTED d >file &&\n+\tgit commit -a -m \"insert line near end of file\" &&\n+\n+\tfill DIRTY a b c INSERTED d >file &&\n+\n+\tgit stash list >stash-before &&\n+\tgit checkout -m nonoverlap_base 2>stderr &&\n+\ttest_grep \"Applied autostash\" stderr &&\n+\ttest_grep ! \"resulted in conflicts\" stderr &&\n+\n+\tgit stash list >stash-after &&\n+\ttest_cmp stash-before stash-after &&\n+\n+\tfill DIRTY a b c d >expect &&\n+\ttest_cmp expect file &&\n+\n+\tgit checkout -f main &&\n+\tgit branch -D nonoverlap_base &&\n+\tgit branch -D nonoverlap_child\n+'\n+\n+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit checkout -m -b newbranch >actual 2>&1 &&\n+\ttest_grep ! \"Created autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tgit checkout main &&\n+\tgit branch -D newbranch\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..cbef8a534e 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"your local changes resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"541720","messageId":"20260416100556.27823-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqfr4xcz7s.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-16T10:05:56Z","receivedAt":"2026-04-16T10:06:00Z","isPatch":true,"body":"> It shows 350+ lines of range-diff to show mostly irrelevant noise,\n> when the true difference between v11 and v12 is only that two helper\n> functions create_autostash_ref_silent{,_with_msg}() are merged into\n> one create_autostash_ref_with_msg_silent() helper function.\n> \n> It is much easier to read that read from the diff between the\n> results of applying v11 and v12 on the same base commit, which is a\n> mere 55 lines (shown at the end).\n> \n> I would not expect you to teach GGG to produce a better range-diff\n> or add an option to instead show an interdiff, but doesn't GGG\n> already have a way to add some human-written comment\n\nI will work on my cover letters, that's a very fair point.\n\nI do think there is some possibility to handle this via maybe a new\noption 'git range-diff --rebase', or directly via GitGitGadget. This would\nautomatically create a diff with only the files actually changed, which\nsaves both author's and reviewer's time.\n\nPerhaps this: https://github.com/gitgitgadget/gitgitgadget/pull/2212\n\n\nHarald\n"},{"id":"541746","messageId":"xmqqpl3z3qwg.fsf@gitster.g","threadId":"65220","inReplyTo":"20260416100556.27823-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-16T14:45:35Z","receivedAt":"2026-04-16T14:45:37Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> I do think there is some possibility to handle this via maybe a new\n> option 'git range-diff --rebase', or directly via GitGitGadget. This would\n> automatically create a diff with only the files actually changed, which\n> saves both author's and reviewer's time.\n\nI am not sure.  Have you actually tried to apply two iterations (I\nthink it was between v11 and v12 but please double check) on the\nsame base and ran range-diff, and compared the result with what I\ncomplained about?  You added one helper in the new iteration, that\nreplaces two helpers you added to the old iteration, and the part of\nthe range-diff that I called \"less interesting\" noise were the\nchange to the callers to the original two helpers to make them call\nthe unified helper, inevitably with different arguments.  I am not\nsure a mechanical textual comparison tool can tell them from the\nmore interesting change that shows that two old helpers did not get\nadded and instead one new unified helper got added.  I do not expect\nthis to change if two versions compared were built on the same base.\n\nAnd that is why I kept saying that the cover letter needs some\ncomments written by the author to guide readers which parts of the\nchanges are notable.\n"},{"id":"541760","messageId":"20260416175323.92708-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"xmqqpl3z3qwg.fsf@gitster.g","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-16T17:53:23Z","receivedAt":"2026-04-16T17:53:27Z","isPatch":true,"body":"> I am not sure.  Have you actually tried to apply two iterations (I\n> think it was between v11 and v12 but please double check) on the\n> same base and ran range-diff, and compared the result with what I\n> complained about?\n\nFair enough, it's not great!\n\n> And that is why I kept saying that the cover letter needs some\n> comments written by the author to guide readers which parts of the\n> changes are notable.\n\nI hear you loud and clear! Next patch will have a better cover letter\nif or when it comes!\n\n\nHarald\n"},{"id":"542030","messageId":"20260421075300.49672-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-21T07:53:00Z","receivedAt":"2026-04-21T07:53:04Z","isPatch":true,"body":"Hi Phillip, did you have a chance to look at the latest changes?\n\n\nHarald\n"},{"id":"542032","messageId":"09d1390e-8334-49e6-a0b5-42d298db4caa@gmail.com","threadId":"65220","inReplyTo":"20260421075300.49672-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-21T09:34:35Z","receivedAt":"2026-04-21T09:34:44Z","isPatch":true,"body":"On 21/04/2026 08:53, Harald Nordgren wrote:\n> Hi Phillip, did you have a chance to look at the latest changes?\n\nNot yet, I should get round to it later this week. Junio is offline for \nat least the next week, I'll make sure I've reviewed them by the time he \nreturns.\n\nThanks\n\nPhillip\n\n> \n> Harald\n\n"},{"id":"542149","messageId":"20260422175825.40701-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"09d1390e-8334-49e6-a0b5-42d298db4caa@gmail.com","subject":"Re: [PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-22T17:58:25Z","receivedAt":"2026-04-22T17:58:29Z","isPatch":true,"body":"👍\n\n\nHarald\n"},{"id":"542260","messageId":"65f77343-2ee6-4ed6-adb2-271814148310@gmail.com","threadId":"65220","inReplyTo":"86f33df1eb043d92cc626092d512aec670c89bdb.1776270259.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v14 5/5] checkout -m: autostash when switching branches","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-24T15:47:34Z","receivedAt":"2026-04-24T15:47:39Z","isPatch":true,"body":"Hi Harald\n\nOn 15/04/2026 17:24, Harald Nordgren via GitGitGadget wrote:\n(trimming the documentation - I'll try and look at that next time)\n\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index c80c62b37b..55c4db04c6 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -17,7 +17,6 @@\n>   #include \"merge-ll.h\"\n>   #include \"lockfile.h\"\n>   #include \"mem-pool.h\"\n> -#include \"merge-ort-wrappers.h\"\n>   #include \"object-file.h\"\n>   #include \"object-name.h\"\n>   #include \"odb.h\"\n> @@ -30,6 +29,7 @@\n>   #include \"repo-settings.h\"\n>   #include \"resolve-undo.h\"\n>   #include \"revision.h\"\n> +#include \"sequencer.h\"\n>   #include \"setup.h\"\n>   #include \"submodule.h\"\n>   #include \"symlinks.h\"\n> @@ -853,90 +853,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\tret = unpack_trees(2, trees, &topts);\n>   \t\tclear_unpack_trees_porcelain(&topts);\n>   \t\tif (ret == -1) {\n> -\t\t\t/*\n> -\t\t\t * Unpack couldn't do a trivial merge; either\n> -\t\t\t * give up or do a real merge, depending on\n> -\t\t\t * whether the merge flag was used.\n> -\t\t\t */\n> -\t\t\tstruct tree *work;\n> -\t\t\tstruct tree *old_tree;\n> -\t\t\tstruct merge_options o;\n> -\t\t\tstruct strbuf sb = STRBUF_INIT;\n> -\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n> -\n> -\t\t\tif (!opts->merge) {\n> -\t\t\t\trollback_lock_file(&lock_file);\n> -\t\t\t\treturn 1;\n> -\t\t\t}\n> -\n> -\t\t\t/*\n> -\t\t\t * Without old_branch_info->commit, the below is the same as\n> -\t\t\t * the two-tree unpack we already tried and failed.\n> -\t\t\t */\n> -\t\t\tif (!old_branch_info->commit) {\n> -\t\t\t\trollback_lock_file(&lock_file);\n> -\t\t\t\treturn 1;\n> -\t\t\t}\n> -\t\t\told_tree = repo_get_commit_tree(the_repository,\n> -\t\t\t\t\t\t\told_branch_info->commit);\n> -\n> -\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n> -\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n> -\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n> -\t\t\tstrbuf_release(&sb);\n> -\n> -\t\t\t/* Do more real merge */\n> -\n> -\t\t\t/*\n> -\t\t\t * We update the index fully, then write the\n> -\t\t\t * tree from the index, then merge the new\n> -\t\t\t * branch with the current tree, with the old\n> -\t\t\t * branch as the base. Then we reset the index\n> -\t\t\t * (but not the working tree) to the new\n> -\t\t\t * branch, leaving the working tree as the\n> -\t\t\t * merged version, but skipping unmerged\n> -\t\t\t * entries in the index.\n> -\t\t\t */\n> -\n> -\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n> -\t\t\t\t\t0, 0);\n> -\t\t\tinit_ui_merge_options(&o, the_repository);\n> -\t\t\to.verbosity = 0;\n> -\t\t\twork = write_in_core_index_as_tree(the_repository,\n> -\t\t\t\t\t\t\t   the_repository->index);\n> -\n> -\t\t\tret = reset_tree(new_tree,\n> -\t\t\t\t\t opts, 1,\n> -\t\t\t\t\t writeout_error, new_branch_info);\n> -\t\t\tif (ret) {\n> -\t\t\t\trollback_lock_file(&lock_file);\n> -\t\t\t\treturn ret;\n> -\t\t\t}\n> -\t\t\to.ancestor = old_branch_info->name;\n> -\t\t\tif (!old_branch_info->name) {\n> -\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n> -\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n> -\t\t\t\t\t\t\t DEFAULT_ABBREV);\n> -\t\t\t\to.ancestor = old_commit_shortname.buf;\n> -\t\t\t}\n> -\t\t\to.branch1 = new_branch_info->name;\n> -\t\t\to.branch2 = \"local\";\n> -\t\t\to.conflict_style = opts->conflict_style;\n> -\t\t\tret = merge_ort_nonrecursive(&o,\n> -\t\t\t\t\t\t     new_tree,\n> -\t\t\t\t\t\t     work,\n> -\t\t\t\t\t\t     old_tree);\n> -\t\t\tif (ret < 0)\n> -\t\t\t\tdie(NULL);\n> -\t\t\tret = reset_tree(new_tree,\n> -\t\t\t\t\t opts, 0,\n> -\t\t\t\t\t writeout_error, new_branch_info);\n> -\t\t\tstrbuf_release(&o.obuf);\n> -\t\t\tstrbuf_release(&old_commit_shortname);\n> -\t\t\tif (ret) {\n> -\t\t\t\trollback_lock_file(&lock_file);\n> -\t\t\t\treturn ret;\n> -\t\t\t}\n> +\t\t\trollback_lock_file(&lock_file);\n> +\t\t\treturn ret;\n\nret is -1 so we return the same value if unpack_trees() fails as do the \nchecks at the top of the function do when they fail with \"return \nerror(...)\". Therefore we cannot determine whether a failure of this \nfunction is due to unpack_trees() or not and so we wont know whether to \nautostash or not. You need to return a unique value here like -2 (or \nideally a named constant)\n\n>   \t\t}\n>   \t}\n>   \n> @@ -1181,6 +1099,10 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \tstruct object_id rev;\n>   \tint flag, writeout_error = 0;\n>   \tint do_merge = 1;\n> +\tint created_autostash = 0;\n> +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n> +\tstruct strbuf autostash_msg = STRBUF_INIT;\n> +\tconst char *stash_label_base = NULL;\n>   \n>   \ttrace2_cmd_mode(\"branch\");\n>   \n> @@ -1218,11 +1140,39 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \t\t\tdo_merge = 0;\n>   \t}\n>   \n> +\tif (old_branch_info.name)\n> +\t\tstash_label_base = old_branch_info.name;\n> +\telse if (old_branch_info.commit) {\n\nStyle: if one branch of an if statement has braces then all branch should.\n\n> +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n> +\t\t\t\t\t &old_branch_info.commit->object.oid,\n> +\t\t\t\t\t DEFAULT_ABBREV);\n> +\t\tstash_label_base = old_commit_shortname.buf;\n> +\t}\n> +\n>   \tif (do_merge) {\n>   \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\tif (ret == -1 && opts->merge) {\n> +\t\t\tstrbuf_addf(&autostash_msg,\n> +\t\t\t\t    \"autostash while switching to '%s'\",\n> +\t\t\t\t    new_branch_info->name);\n> +\t\t\tcreate_autostash_ref(the_repository,\n> +\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t     autostash_msg.buf, true);\n> +\t\t\tcreated_autostash = 1;\n> +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\t}\n>   \t\tif (ret) {\n> +\t\t\tif (created_autostash)\n> +\t\t\t\tapply_autostash_ref(the_repository,\n> +\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t\t    new_branch_info->name,\n> +\t\t\t\t\t\t    \"local\",\n> +\t\t\t\t\t\t    stash_label_base,\n> +\t\t\t\t\t\t    autostash_msg.buf);\n\nGood - now we only try to restore the stashed changes if we actually \nstashed. However we only restore the stashed changes if there was an \nerror(). If there isn't an error we call update_refs_for_switch() before \nrestoring them. It would be safer to restore them straight away in case \nthat function ends up dying for any reason (though I think that's pretty \nunlikely)\n\n\tif (created_autostash) {\n\t\tif (opts->conflict_style >= 0)\n\t\t\t/* set up confilct style */\n\t\tapply_autostash_ref(...);\n\t}\n\tif (ret) {\n\n>   \t\t\tbranch_info_release(&old_branch_info);\n> -\t\t\treturn ret;\n> +\t\t\tstrbuf_release(&old_commit_shortname);\n> +\t\t\tstrbuf_release(&autostash_msg);\n> +\t\t\treturn ret < 0 ? 1 : ret;\n\nThis changes the return value for all errors from merge_working_tree() - \nthat's probably a good this as this value is used for the exit code and \nwe don't really want an exit code of -1\n\n>   \t\t}\n>   \t}\n>   \n> @@ -1231,8 +1181,30 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \n>   \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n>   \n> +\tif (opts->conflict_style >= 0) {\n> +\t\tstruct strbuf cfg = STRBUF_INIT;\n> +\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n> +\t\t\t    conflict_style_name(opts->conflict_style));\n> +\t\tgit_config_push_parameter(cfg.buf);\n> +\t\tstrbuf_release(&cfg);\n> +\t}\n> +\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t    new_branch_info->name, \"local\",\n> +\t\t\t    stash_label_base,\n> +\t\t\t    autostash_msg.buf);> +\tdiscard_index(the_repository->index);\n\nAs I said last time we should not be calling apply_autostash() if we \nhave not created an autostash. We should also not discard and re-read \nthe index if we haven't stashed. I do think we'd be better restoring the \nstashed changes in a single place as I said above.\n\n> +\tif (repo_read_index(the_repository) < 0)\n> +\t\tdie(_(\"index file corrupt\"));\n> +\n> +\tif (created_autostash && !opts->quiet && new_branch_info->commit)\n> +\t\tshow_local_changes(&new_branch_info->commit->object,\n> +\t\t\t\t   &opts->diff_options);\n\nThis shows the local changes, but it doesn't give any explanation of \nwhat the output is. For example when switching branches with a conflict \nI see\n\nYour local changes are stashed, however, applying it to carry\nforward your local changes resulted in conflicts:\n\n  - You can try resolving them now.  If you resolved them\n    successfully, discard the stash entry with \"git stash drop\".\n\n  - Alternatively you can \"git reset --hard\" if you do not want\n    to deal with them right now, and later \"git stash pop\" to\n    recover your local changes.\nM\tt/t7201-co.sh\n\nwhere the changes appear to be part of the advice message. Perhaps we \nshould print a short (i.e. one sentance) message along the lines of\n\n\tThe following paths have local changes\n\nWe should test what the user sees here as well.\n\n> +\n>   \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n>   \tbranch_info_release(&old_branch_info);\n> +\tstrbuf_release(&old_commit_shortname);\n> +\tstrbuf_release(&autostash_msg);\n>   \n>   \treturn ret || writeout_error;\n>   }\n> diff --git a/sequencer.c b/sequencer.c\n> index 7c0376d9e4..480e8e6c0b 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4765,15 +4765,23 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n>   \t\tstrvec_push(&store.args, stash_oid);\n>   \t\tif (run_command(&store))\n>   \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n> +\t\telse if (attempt_apply)\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n> +\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n\nI'm not sure we need to say \"local changes\" twice here\n\n\tYour local changes are stashed, however applying them\n\tresulted in conflicts.\n\n> +\t\t\t\t  \"\\n\"\n> +\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n> +\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n\ns/resolved/resolve/\n\n> +\t\t\t\t  \"\\n\"\n> +\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n> +\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n> +\t\t\t\t  \"   recover your local changes.\\n\"));\n\nI find the bulleted list a bit odd, maybe\n\n\tYou can either resolve the conflicts and then discard the stash\n  \twith \"git stash drop\", or, if you do not want to resolve them\n\tnow, run \"git reset --hard\" and apply the local changes later by\n\trunning \"git stash pop\"\n\nwould be better?\n\n>   \t\telse\n>   \t\t\tfprintf(stderr,\n> -\t\t\t\t_(\"%s\\n\"\n> +\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n>   \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n>   \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n> -\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n> -\t\t\t\tattempt_apply ?\n> -\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n> -\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n> +\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n>   \t}\n>   \n>   \treturn ret;\n> diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\n> index ad3ba6a984..e4e2cb19ce 100755\n> --- a/t/t3420-rebase-autostash.sh\n> +++ b/t/t3420-rebase-autostash.sh\n> @@ -61,18 +61,30 @@ create_expected_failure_apply () {\n>   \tFirst, rewinding head to replay your work on top of it...\n>   \tApplying: second commit\n>   \tApplying: third commit\n> -\tApplying autostash resulted in conflicts.\n> -\tYour changes are safe in the stash.\n> -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n> +\tYour local changes are stashed, however, applying it to carry\n> +\tforward your local changes resulted in conflicts:\n> +\n> +\t - You can try resolving them now.  If you resolved them\n> +\t   successfully, discard the stash entry with \"git stash drop\".\n> +\n> +\t - Alternatively you can \"git reset --hard\" if you do not want\n> +\t   to deal with them right now, and later \"git stash pop\" to\n> +\t   recover your local changes.\n>   \tEOF\n>   }\n>   \n>   create_expected_failure_merge () {\n>   \tcat >expected <<-EOF\n>   \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n> -\tApplying autostash resulted in conflicts.\n> -\tYour changes are safe in the stash.\n> -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n> +\tYour local changes are stashed, however, applying it to carry\n> +\tforward your local changes resulted in conflicts:\n> +\n> +\t - You can try resolving them now.  If you resolved them\n> +\t   successfully, discard the stash entry with \"git stash drop\".\n> +\n> +\t - Alternatively you can \"git reset --hard\" if you do not want\n> +\t   to deal with them right now, and later \"git stash pop\" to\n> +\t   recover your local changes.\n>   \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n>   \tEOF\n>   }\n> diff --git a/t/t7201-co.sh b/t/t7201-co.sh\n> index 9bcf7c0b40..c474c6759f 100755\n> --- a/t/t7201-co.sh\n> +++ b/t/t7201-co.sh\n> @@ -210,6 +210,214 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n>   \ttest_cmp expect two\n>   '\n>   \n> +test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\tfill a b X d e >two &&\n> +\tgit checkout --merge --conflict=zdiff3 simple &&\n\nIf I change \"zdiff3\" to \"diff3\" this test still passes which is \ndisappointing. As the code that parses the conflict style is shared with \nother commands and we already have tests for --conflict=diff3 and \n--conflict=merge I'm not sure this test adds much.\n\n> +\n> +\tcat <<-EOF >expect &&\n> +\ta\n> +\t<<<<<<< simple\n> +\tc\n> +\t||||||| main\n> +\tb\n> +\tc\n> +\td\n> +\t=======\n> +\tb\n> +\tX\n> +\td\n> +\t>>>>>>> local\n> +\te\n> +\tEOF\n> +\ttest_cmp expect two\n> +'\n> +\n> +test_expect_success 'checkout -m respects merge.conflictStyle config' '\n\nLooking at the existing tests, 'checkout with --merge, in diff3 -m \nstyle' and 'checkout --conflict=merge, overriding config' already test \nthat we respect merge.conflictStyle and that --conflict overrides it so \nI don't see what new coverage this test adds.\n\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\ttest_config merge.conflictStyle diff3 &&\n> +\tfill b d >two &&\n> +\tgit checkout -m simple &&\n> +\n> +\tcat <<-EOF >expect &&\n> +\t<<<<<<< simple\n> +\ta\n> +\tc\n> +\te\n> +\t||||||| main\n> +\ta\n> +\tb\n> +\tc\n> +\td\n> +\te\n> +\t=======\n> +\tb\n> +\td\n> +\t>>>>>>> local\n> +\tEOF\n> +\ttest_cmp expect two\n> +'\n> +\n> +test_expect_success 'checkout -m skips stash when no conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit stash list >stash-before &&\n> +\tgit checkout -m side >actual 2>&1 &&\n\nfile \"same\" is unchanged between branch \"side\" and \"branch\" main so we \ndo not need to stash it.\n\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tgit stash list >stash-after &&\n> +\ttest_cmp stash-before stash-after &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same\n\nEven if we created an autostash this test would not pick it up as the \nstash is not written to refs/stash unless there are merge conflicts and \nwe don't print \"Created autostash\" even when we do create an autostash. \nThe same is true for \"checkout -m -b skips stash with dirty tree\" below. \nI don't see how we can check that a stash was not created without using \nGIT_TRACE to see if we run \"git stash\". Even that is fragile as we might \nstart stashing without forking a separate process in future.\n\n> +'\n> +\n> +test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit add same &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same\n> +'\n> +\n> +test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 6 7 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"Applied autostash\" actual &&\n> +\tfill 1 2 3 4 5 6 7 >expect &&\n> +\ttest_cmp expect one\n> +'\n\nI don't think the two tests above add any extra coverage when we have \nthe one below so they can be deleted. Our test suite is slow enough \nalready - we only need one test to fail for any given issue.\n\n> +test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit add same &&\n> +\tfill 1 2 3 4 5 6 7 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"Applied autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same &&\n> +\tfill 1 2 3 4 5 6 7 >expect &&\n> +\ttest_cmp expect one\n> +'\n> +\n> +test_expect_success 'checkout -m stashes on truly conflicting changes' '\n\nThis use of conflicting is rather confusing - what's the difference \nbetween a conflicting change and a truly conflicting change?\n\nI think a single test is sufficient to check that we create a valid \nstash entry\n\ntest_expect_success 'checkout -m stashes on truly conflicting changes' '\n\tgit checkout -f main &&\n\tgit clean -f &&\n\n\tfill 1 2 3 4 5 >one &&\n\ttest_must_fail git checkout side 2>stderr &&\n\ttest_grep \"Your local changes\" stderr &&\n\tgit checkout -m side >actual 2>&1 &&\n\ttest_grep ! \"Created autostash\" actual &&\n\ttest_grep \"resulted in conflicts\" actual &&\n\ttest_grep \"git stash drop\" actual &&\n\ttest_grep \"recover your local changes\" actual &&\n\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n\tsed /^index/d actual >actual.trimmed &&\n\tcat >expect <<-EOF &&\n\tOn main: autostash while switching to ${SQ}side${SQ}\n\tdiff --git a/one b/one\n\t--- a/one\n\t+++ b/one\n\t@@ -3,6 +3,3 @@\n\t 3\n\t 4\n\t 5\n\t-6\n\t-7\n\t-8\n\tEOF\n\ttest_cmp expect actual.trimmed &&\n'\n\nThen we can delete from here to ...\n\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\ttest_must_fail git checkout side 2>stderr &&\n> +\ttest_grep \"Your local changes\" stderr &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"resulted in conflicts\" actual &&\n> +\ttest_grep \"git stash drop\" actual &&\n> +\tgit stash drop &&\n> +\tgit reset --hard\n> +'\n> +\n> +test_expect_success 'checkout -m produces usable stash on conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep \"recover your local changes\" actual &&\n> +\tgit checkout -f main &&\n> +\tgit stash pop &&\n> +\tfill 1 2 3 4 5 >expect &&\n> +\ttest_cmp expect one\n> +'\n> +\n> +test_expect_success 'checkout -m autostash message includes target branch' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\tgit stash list >stash-list &&\n> +\ttest_grep \"autostash while switching to .side.\" stash-list &&\n> +\tgit stash drop &&\n> +\tgit checkout -f main &&\n> +\tgit reset --hard\n> +'\n> +\n> +test_expect_success 'checkout -m stashes on staged conflicting changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\tgit add one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\ttest_grep \"resulted in conflicts\" actual &&\n> +\ttest_grep \"git stash drop\" actual &&\n> +\tgit stash drop &&\n> +\tgit reset --hard\n> +'\n\n... here\n\n\n> +test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n\nI've no idea what this is trying to do - it looks more like it is \ntesting that \"git stash\" works rather than anything to do with \"git \ncheckout\"\n\n> +\tgit checkout -f main &&\n> +\tgit reset --hard &&\n> +\tgit clean -f &&\n> +\n> +\tgit checkout -b nonoverlap_base &&\n> +\tfill a b c d >file &&\n> +\tgit add file &&\n> +\tgit commit -m \"add file\" &&\n> +\n> +\tgit checkout -b nonoverlap_child &&\n> +\tfill a b c INSERTED d >file &&\n> +\tgit commit -a -m \"insert line near end of file\" &&\n> +\n> +\tfill DIRTY a b c INSERTED d >file &&\n> +\n> +\tgit stash list >stash-before &&\n> +\tgit checkout -m nonoverlap_base 2>stderr &&\n> +\ttest_grep \"Applied autostash\" stderr &&\n> +\ttest_grep ! \"resulted in conflicts\" stderr &&\n> +\n> +\tgit stash list >stash-after &&\n> +\ttest_cmp stash-before stash-after &&\n> +\n> +\tfill DIRTY a b c d >expect &&\n> +\ttest_cmp expect file &&\n> +\n> +\tgit checkout -f main &&\n> +\tgit branch -D nonoverlap_base &&\n> +\tgit branch -D nonoverlap_child\n> +'\n> +\n> +test_expect_success 'checkout -m -b skips stash with dirty tree' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit checkout -m -b newbranch >actual 2>&1 &&\n> +\ttest_grep ! \"Created autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same &&\n> +\tgit checkout main &&\n> +\tgit branch -D newbranch\n> +'\n\nAs I said above I don't think this test is testing what it claims to.\n\nI'd suggest adding the following test\n\ntest_expect_success 'checkout -m which would overwrite untracked file' '\n\tgit checkout -f --detach main &&\n\ttest_commit another-file &&\n\tgit checkout HEAD^ &&\n\t>another-file.t &&\n\ttest_must_fail git checkout -m @{-1} 2>err &&\n\ttest_grep \"another-file.t.*overwritten\" err\n'\n\nwhich passes on master but fails with these patches applied. We need to \nmake sure that we don't set \"quiet\" in unpack_tree_opts the second time \nwe call merge_working_tree(). The test could be improved by adding some \nlocal changes.\n\nThis is looking better, but there are still a couple of problems that \nneed addressing before it can be considered ready for merging.\n\nThanks\n\nPhillip\n\n>   test_expect_success 'switch to another branch while carrying a deletion' '\n>   \tgit checkout -f main &&\n>   \tgit reset --hard &&\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index 9838094b66..cbef8a534e 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -914,7 +914,7 @@ test_expect_success 'merge with conflicted --autostash changes' '\n>   \tgit diff >expect &&\n>   \ttest_when_finished \"test_might_fail git stash drop\" &&\n>   \tgit merge --autostash c3 2>err &&\n> -\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n> +\ttest_grep \"your local changes resulted in conflicts\" err &&\n>   \tgit show HEAD:file >merge-result &&\n>   \ttest_cmp result.1-9 merge-result &&\n>   \tgit stash show -p >actual &&\n> diff --git a/xdiff-interface.c b/xdiff-interface.c\n> index f043330f2a..5ee2b96d0a 100644\n> --- a/xdiff-interface.c\n> +++ b/xdiff-interface.c\n> @@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n>   \t\treturn -1;\n>   }\n>   \n> +const char *conflict_style_name(int style)\n> +{\n> +\tswitch (style) {\n> +\tcase XDL_MERGE_DIFF3:\n> +\t\treturn \"diff3\";\n> +\tcase XDL_MERGE_ZEALOUS_DIFF3:\n> +\t\treturn \"zdiff3\";\n> +\tdefault:\n> +\t\treturn \"merge\";\n> +\t}\n> +}\n> +\n>   int git_xmerge_style = -1;\n>   \n>   int git_xmerge_config(const char *var, const char *value,\n> diff --git a/xdiff-interface.h b/xdiff-interface.h\n> index fbc4ceec40..ce54e1c0e0 100644\n> --- a/xdiff-interface.h\n> +++ b/xdiff-interface.h\n> @@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n>   void xdiff_clear_find_func(xdemitconf_t *xecfg);\n>   struct config_context;\n>   int parse_conflict_style_name(const char *value);\n> +const char *conflict_style_name(int style);\n>   int git_xmerge_config(const char *var, const char *value,\n>   \t\t      const struct config_context *ctx, void *cb);\n>   extern int git_xmerge_style;\n\n"},{"id":"542261","messageId":"04a16996-0ce2-45b9-af54-87c9d44659ff@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"Re: [PATCH v14 0/5] checkout: 'autostash' for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-24T15:52:50Z","receivedAt":"2026-04-24T15:52:54Z","isPatch":true,"body":"Hi Harald\n\nOn 15/04/2026 17:24, Harald Nordgren via GitGitGadget wrote:\n> Simplifying the tests in t/t3903-stash.sh according to Phillip Wood's\n> comment. I believe everything sound be fixed now or responded to.\n\nThis is better than the last cover letter, but it would be helpful to \ndescribe the changes you have made rather than just say your responding \nto a reviewers comment. Junio has given some suggestions for \ncoverletters to take inspiration from.\n\nAlso when you reply to messages can you try and keep the subject line \nplease - nearly all the responses from you have the subject\n\nRe: [PATCH] checkout: add --autostash option for branch switching\n\nregardless of the message they're actually replying to. That makes it \nhard to see which message you're replying to.\n\nI've left some comments on patch 5, it is definitely looking better but \nthere are still a couple of things that need fixing.\n\nThanks\n\nPhillip\n\n> Also rebasing against upstream.\n> \n> Harald Nordgren (5):\n>    stash: add --label-ours, --label-theirs, --label-base for apply\n>    sequencer: allow create_autostash to run silently\n>    sequencer: teach autostash apply to take optional conflict marker\n>      labels\n>    checkout: rollback lock on early returns in merge_working_tree\n>    checkout -m: autostash when switching branches\n> \n>   Documentation/git-checkout.adoc |  58 ++++-----\n>   Documentation/git-stash.adoc    |  11 +-\n>   Documentation/git-switch.adoc   |  33 ++---\n>   builtin/checkout.c              | 149 +++++++++++------------\n>   builtin/commit.c                |   3 +-\n>   builtin/merge.c                 |  15 ++-\n>   builtin/stash.c                 |  28 +++--\n>   sequencer.c                     |  73 ++++++++---\n>   sequencer.h                     |   7 +-\n>   t/t3420-rebase-autostash.sh     |  24 +++-\n>   t/t3903-stash.sh                |  24 ++++\n>   t/t7201-co.sh                   | 208 ++++++++++++++++++++++++++++++++\n>   t/t7600-merge.sh                |   2 +-\n>   xdiff-interface.c               |  12 ++\n>   xdiff-interface.h               |   1 +\n>   xdiff/xmerge.c                  |   6 +-\n>   16 files changed, 483 insertions(+), 171 deletions(-)\n> \n> \n> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v14\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v14\n> Pull-Request: https://github.com/git/git/pull/2234\n> \n> Range-diff vs v13:\n> \n>   1:  43bfdf2136 ! 1:  e18c25599a stash: add --label-ours, --label-theirs, --label-base for apply\n>       @@ builtin/stash.c: static int branch_stash(int argc, const char **argv, const char\n>         \n>        \n>         ## t/t3903-stash.sh ##\n>       -@@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit conflicts' '\n>       - \t)\n>       +@@ t/t3903-stash.sh: setup_stash() {\n>       + \tgit add other-file &&\n>       + \ttest_tick &&\n>       + \tgit commit -m initial &&\n>       ++\tgit tag initial &&\n>       + \techo 2 >file &&\n>       + \tgit add file &&\n>       + \techo 3 >file &&\n>       +@@ t/t3903-stash.sh: test_expect_success 'stash.index=false overridden by --index' '\n>       + \ttest_cmp expect file\n>         '\n>         \n>        +test_expect_success 'apply with custom conflict labels' '\n>       -+\ttest_when_finished \"git reset --hard && git stash drop\" &&\n>       -+\tgit reset --hard &&\n>       ++\tgit reset --hard initial &&\n>        +\ttest_commit label-base conflict-file base-content &&\n>        +\techo stashed >conflict-file &&\n>        +\tgit stash push -m \"stashed\" &&\n>       @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n>        +'\n>        +\n>        +test_expect_success 'apply with empty conflict labels' '\n>       -+\ttest_when_finished \"git reset --hard && git stash drop\" &&\n>       -+\tgit reset --hard &&\n>       ++\tgit reset --hard initial &&\n>        +\ttest_commit empty-label-base conflict-file base-content &&\n>        +\techo stashed >conflict-file &&\n>        +\tgit stash push -m \"stashed\" &&\n>       @@ t/t3903-stash.sh: test_expect_success 'restore untracked files even when we hit\n>        +\ttest_grep \"^>>>>>>>$\" conflict-file\n>        +'\n>        +\n>       - test_expect_success 'stash create reports a locked index' '\n>       - \ttest_when_finished \"rm -rf repo\" &&\n>       - \tgit init repo &&\n>       + test_done\n>        \n>         ## xdiff/xmerge.c ##\n>        @@ xdiff/xmerge.c: static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n>   2:  7f3c32f5e9 = 2:  ce29b10264 sequencer: allow create_autostash to run silently\n>   3:  b279d1dac8 = 3:  73051d1762 sequencer: teach autostash apply to take optional conflict marker labels\n>   4:  04869314ec = 4:  191058d8e3 checkout: rollback lock on early returns in merge_working_tree\n>   5:  4b3c6025ac = 5:  86f33df1eb checkout -m: autostash when switching branches\n> \n\n"},{"id":"542274","messageId":"20260424205237.65227-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"65f77343-2ee6-4ed6-adb2-271814148310@gmail.com","subject":"Comments on Phillip's review","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-24T20:52:37Z","receivedAt":"2026-04-24T20:52:40Z","isPatch":false,"body":"> ret is -1 so we return the same value if unpack_trees() fails as do the\n> checks at the top of the function do when they fail with \"return\n> error(...)\". Therefore we cannot determine whether a failure of this\n> function is due to unpack_trees() or not and so we wont know whether to\n> autostash or not. You need to return a unique value here like -2 (or\n> ideally a named constant)\n\n👍\n\n> Good - now we only try to restore the stashed changes if we actually\n> stashed. However we only restore the stashed changes if there was an\n> error(). If there isn't an error we call update_refs_for_switch() before\n> restoring them. It would be safer to restore them straight away in case\n> that function ends up dying for any reason (though I think that's pretty\n> unlikely)\n\nI hope I understand correctly, the code becomes easier this way, so that's\nnice!\n\n> As I said last time we should not be calling apply_autostash() if we\n> have not created an autostash. We should also not discard and re-read\n> the index if we haven't stashed. I do think we'd be better restoring the\n> stashed changes in a single place as I said above.\n\nMakes sense.\n\n> where the changes appear to be part of the advice message. Perhaps we\n> should print a short (i.e. one sentance) message along the lines of\n> \n>         The following paths have local changes\n> \n> We should test what the user sees here as well.\n\nAdd that message.\n\nDo you mean to test the full output? I'm not against it at all, but that\nseems to be going against the convention of the other tests in this file.\nBut it would be a more robust test.\n\n> I'm not sure we need to say \"local changes\" twice here\n\n👍\n\n> I find the bulleted list a bit odd, maybe\n> \n>         You can either resolve the conflicts and then discard the stash\n>         with \"git stash drop\", or, if you do not want to resolve them\n>         now, run \"git reset --hard\" and apply the local changes later by\n>         running \"git stash pop\"\n> \n> would be better?\n\nMuch cleaner, thanks!\n\n> we already have tests for --conflict=diff3 and\n> --conflict=merge I'm not sure this test adds much.\n\nDeleted.\n\n> > +\n> > +     cat <<-EOF >expect &&\n> > +     a\n> > +     <<<<<<< simple\n> > +     c\n> > +     ||||||| main\n> > +     b\n> > +     c\n> > +     d\n> > +     =======\n> > +     b\n> > +     X\n> > +     d\n> > +     >>>>>>> local\n> > +     e\n> > +     EOF\n> > +     test_cmp expect two\n> > +'\n> > +\n> > +test_expect_success 'checkout -m respects merge.conflictStyle config' '\n> \n> Looking at the existing tests, 'checkout with --merge, in diff3 -m\n> style' and 'checkout --conflict=merge, overriding config' already test\n> that we respect merge.conflictStyle and that --conflict overrides it so\n> I don't see what new coverage this test adds.\n\nDeleted.\n\n> > +test_expect_success 'checkout -m skips stash when no conflict' '\n> > +     git checkout -f main &&\n> > +     git clean -f &&\n> > +\n> > +     fill 0 x y z >same &&\n> > +     git stash list >stash-before &&\n> > +     git checkout -m side >actual 2>&1 &&\n> \n> file \"same\" is unchanged between branch \"side\" and \"branch\" main so we\n> do not need to stash it.\n\nDeleted.\n\n> > +     test_grep ! \"Created autostash\" actual &&\n> > +     git stash list >stash-after &&\n> > +     test_cmp stash-before stash-after &&\n> > +     fill 0 x y z >expect &&\n> > +     test_cmp expect same\n> \n> Even if we created an autostash this test would not pick it up as the\n> stash is not written to refs/stash unless there are merge conflicts and\n> we don't print \"Created autostash\" even when we do create an autostash.\n> The same is true for \"checkout -m -b skips stash with dirty tree\" below.\n> I don't see how we can check that a stash was not created without using\n> GIT_TRACE to see if we run \"git stash\". Even that is fragile as we might\n> start stashing without forking a separate process in future.\n\nDeleted.\n\n> I don't think the two tests above add any extra coverage when we have\n> the one below so they can be deleted. Our test suite is slow enough\n> already - we only need one test to fail for any given issue.\n\nDeleted.\n\n> > +test_expect_success 'checkout -m stashes on truly conflicting changes' '\n> \n> This use of conflicting is rather confusing - what's the difference\n> between a conflicting change and a truly conflicting change?\n> \n> I think a single test is sufficient to check that we create a valid\n> stash entry\n\nUpdated.\n\n> test_expect_success 'checkout -m which would overwrite untracked file' '\n>         git checkout -f --detach main &&\n>         test_commit another-file &&\n>         git checkout HEAD^ &&\n>         >another-file.t &&\n>         test_must_fail git checkout -m @{-1} 2>err &&\n>         test_grep \"another-file.t.*overwritten\" err\n> '\n> \n> which passes on master but fails with these patches applied. We need to\n> make sure that we don't set \"quiet\" in unpack_tree_opts the second time\n> we call merge_working_tree(). The test could be improved by adding some\n> local changes.\n\nTricky to get right, the test if very good to have! I rewrote the logic now\nto make this test pass, I hope it looks better now.\n\n\nHarald\n"},{"id":"542276","messageId":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v14.git.git.1776270259.gitgitgadget@gmail.com","subject":"[PATCH v15 0/5] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-24T21:10:07Z","receivedAt":"2026-04-24T21:10:15Z","isPatch":true,"body":" * When the initial merge fails, the caller now knows for sure it was a\n   conflict (not some unrelated error) before kicking off the autostash.\n\n * If the second attempt also fails, the user now actually sees why.\n   Previously errors like \"would be overwritten by checkout\" were being\n   swallowed.\n\n * The stash is reapplied right after the second attempt, so it won't get\n   left behind if something else goes wrong later in the checkout.\n\n * A couple of clean-up steps only run when we actually stashed, so regular\n   checkouts aren't paying for work they don't need.\n\n * Added a small header \"The following paths have local changes:\" before the\n   list of changed files, so it doesn't look like part of the conflict\n   advice above it.\n\n * Reworded the conflict advice into a single paragraph. Feels less shouty\n   and doesn't say \"local changes\" three times.\n\n * Trimmed and consolidated tests in t7201. A bunch of the old ones couldn't\n   actually fail, or were really testing other things; the conflict-path\n   tests are now a single test that checks the stash entry directly.\n\nHarald Nordgren (5):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: rollback lock on early returns in merge_working_tree\n  checkout -m: autostash when switching branches\n\n Documentation/git-checkout.adoc |  58 +++++------\n Documentation/git-stash.adoc    |  11 ++-\n Documentation/git-switch.adoc   |  33 +++----\n builtin/checkout.c              | 165 +++++++++++++++-----------------\n builtin/commit.c                |   3 +-\n builtin/merge.c                 |  15 ++-\n builtin/stash.c                 |  28 ++++--\n sequencer.c                     |  69 +++++++++----\n sequencer.h                     |   7 +-\n t/t3420-rebase-autostash.sh     |  16 ++--\n t/t3903-stash.sh                |  24 +++++\n t/t7201-co.sh                   |  61 +++++++++++-\n t/t7600-merge.sh                |   3 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 16 files changed, 335 insertions(+), 177 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v15\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v15\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v14:\n\n 1:  e18c25599a = 1:  aba8e6a9dc stash: add --label-ours, --label-theirs, --label-base for apply\n 2:  ce29b10264 = 2:  89e0bfa803 sequencer: allow create_autostash to run silently\n 3:  73051d1762 = 3:  a428ce7328 sequencer: teach autostash apply to take optional conflict marker labels\n 4:  191058d8e3 = 4:  f358424085 checkout: rollback lock on early returns in merge_working_tree\n 5:  86f33df1eb ! 5:  96b14db827 checkout -m: autostash when switching branches\n     @@ builtin/checkout.c\n       #include \"setup.h\"\n       #include \"submodule.h\"\n       #include \"symlinks.h\"\n     +@@ builtin/checkout.c: struct checkout_opts {\n     + \t.auto_advance = 1, \\\n     + }\n     + \n     ++#define MERGE_WORKING_TREE_UNPACK_FAILED (-2)\n     ++\n     + struct branch_info {\n     + \tchar *name; /* The short name used */\n     + \tchar *path; /* The full name of a real branch */\n     +@@ builtin/checkout.c: static void setup_branch_path(struct branch_info *branch)\n     + \n     + static void init_topts(struct unpack_trees_options *topts, int merge,\n     + \t\t       int show_progress, int overwrite_ignore,\n     +-\t\t       struct commit *old_commit)\n     ++\t\t       struct commit *old_commit, bool show_unpack_errors)\n     + {\n     + \tmemset(topts, 0, sizeof(*topts));\n     + \ttopts->head_idx = -1;\n     +@@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, int merge,\n     + \ttopts->initial_checkout = is_index_unborn(the_repository->index);\n     + \ttopts->update = 1;\n     + \ttopts->merge = 1;\n     +-\ttopts->quiet = merge && old_commit;\n     ++\ttopts->quiet = merge && old_commit && !show_unpack_errors;\n     + \ttopts->verbose_update = show_progress;\n     + \ttopts->fn = twoway_merge;\n     + \ttopts->preserve_ignored = !overwrite_ignore;\n     +@@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, int merge,\n     + static int merge_working_tree(const struct checkout_opts *opts,\n     + \t\t\t      struct branch_info *old_branch_info,\n     + \t\t\t      struct branch_info *new_branch_info,\n     +-\t\t\t      int *writeout_error)\n     ++\t\t\t      int *writeout_error,\n     ++\t\t\t      bool show_unpack_errors)\n     + {\n     + \tint ret;\n     + \tstruct lock_file lock_file = LOCK_INIT;\n     +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \n     + \t\t/* 2-way merge to the new branch */\n     + \t\tinit_topts(&topts, opts->merge, opts->show_progress,\n     +-\t\t\t   opts->overwrite_ignore, old_branch_info->commit);\n     ++\t\t\t   opts->overwrite_ignore, old_branch_info->commit,\n     ++\t\t\t   show_unpack_errors);\n     + \t\tinit_checkout_metadata(&topts.meta, new_branch_info->refname,\n     + \t\t\t\t       new_branch_info->commit ?\n     + \t\t\t\t       &new_branch_info->commit->object.oid :\n      @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n       \t\tret = unpack_trees(2, trees, &topts);\n       \t\tclear_unpack_trees_porcelain(&topts);\n     @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      -\t\t\t\treturn ret;\n      -\t\t\t}\n      +\t\t\trollback_lock_file(&lock_file);\n     -+\t\t\treturn ret;\n     ++\t\t\treturn MERGE_WORKING_TREE_UNPACK_FAILED;\n       \t\t}\n       \t}\n       \n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \t\t\tdo_merge = 0;\n       \t}\n       \n     -+\tif (old_branch_info.name)\n     ++\tif (old_branch_info.name) {\n      +\t\tstash_label_base = old_branch_info.name;\n     -+\telse if (old_branch_info.commit) {\n     ++\t} else if (old_branch_info.commit) {\n      +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n      +\t\t\t\t\t &old_branch_info.commit->object.oid,\n      +\t\t\t\t\t DEFAULT_ABBREV);\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t}\n      +\n       \tif (do_merge) {\n     - \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n     -+\t\tif (ret == -1 && opts->merge) {\n     +-\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n     ++\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n     ++\t\t\t\t\t &writeout_error, false);\n     ++\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n      +\t\t\tstrbuf_addf(&autostash_msg,\n      +\t\t\t\t    \"autostash while switching to '%s'\",\n      +\t\t\t\t    new_branch_info->name);\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n      +\t\t\t\t\t     autostash_msg.buf, true);\n      +\t\t\tcreated_autostash = 1;\n     -+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n     ++\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n     ++\t\t\t\t\t\t &writeout_error, true);\n     ++\t\t}\n     ++\t\tif (created_autostash) {\n     ++\t\t\tif (opts->conflict_style >= 0) {\n     ++\t\t\t\tstruct strbuf cfg = STRBUF_INIT;\n     ++\t\t\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n     ++\t\t\t\t\t    conflict_style_name(opts->conflict_style));\n     ++\t\t\t\tgit_config_push_parameter(cfg.buf);\n     ++\t\t\t\tstrbuf_release(&cfg);\n     ++\t\t\t}\n     ++\t\t\tapply_autostash_ref(the_repository,\n     ++\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n     ++\t\t\t\t\t    new_branch_info->name,\n     ++\t\t\t\t\t    \"local\",\n     ++\t\t\t\t\t    stash_label_base,\n     ++\t\t\t\t\t    autostash_msg.buf);\n      +\t\t}\n       \t\tif (ret) {\n     -+\t\t\tif (created_autostash)\n     -+\t\t\t\tapply_autostash_ref(the_repository,\n     -+\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n     -+\t\t\t\t\t\t    new_branch_info->name,\n     -+\t\t\t\t\t\t    \"local\",\n     -+\t\t\t\t\t\t    stash_label_base,\n     -+\t\t\t\t\t\t    autostash_msg.buf);\n       \t\t\tbranch_info_release(&old_branch_info);\n      -\t\t\treturn ret;\n      +\t\t\tstrbuf_release(&old_commit_shortname);\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \n       \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n       \n     -+\tif (opts->conflict_style >= 0) {\n     -+\t\tstruct strbuf cfg = STRBUF_INIT;\n     -+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n     -+\t\t\t    conflict_style_name(opts->conflict_style));\n     -+\t\tgit_config_push_parameter(cfg.buf);\n     -+\t\tstrbuf_release(&cfg);\n     ++\tif (created_autostash) {\n     ++\t\tdiscard_index(the_repository->index);\n     ++\t\tif (repo_read_index(the_repository) < 0)\n     ++\t\t\tdie(_(\"index file corrupt\"));\n     ++\n     ++\t\tif (!opts->quiet && new_branch_info->commit) {\n     ++\t\t\tprintf(_(\"The following paths have local changes:\\n\"));\n     ++\t\t\tshow_local_changes(&new_branch_info->commit->object,\n     ++\t\t\t\t\t   &opts->diff_options);\n     ++\t\t}\n      +\t}\n     -+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n     -+\t\t\t    new_branch_info->name, \"local\",\n     -+\t\t\t    stash_label_base,\n     -+\t\t\t    autostash_msg.buf);\n     -+\n     -+\tdiscard_index(the_repository->index);\n     -+\tif (repo_read_index(the_repository) < 0)\n     -+\t\tdie(_(\"index file corrupt\"));\n     -+\n     -+\tif (created_autostash && !opts->quiet && new_branch_info->commit)\n     -+\t\tshow_local_changes(&new_branch_info->commit->object,\n     -+\t\t\t\t   &opts->diff_options);\n      +\n       \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n       \tbranch_info_release(&old_branch_info);\n     @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n       \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n      +\t\telse if (attempt_apply)\n      +\t\t\tfprintf(stderr,\n     -+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n     -+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n     -+\t\t\t\t  \"\\n\"\n     -+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n     -+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n     -+\t\t\t\t  \"\\n\"\n     -+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n     -+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n     -+\t\t\t\t  \"   recover your local changes.\\n\"));\n     ++\t\t\t\t_(\"Your local changes are stashed, however applying them\\n\"\n     ++\t\t\t\t  \"resulted in conflicts.  You can either resolve the conflicts\\n\"\n     ++\t\t\t\t  \"and then discard the stash with \\\"git stash drop\\\", or, if you\\n\"\n     ++\t\t\t\t  \"do not want to resolve them now, run \\\"git reset --hard\\\" and\\n\"\n     ++\t\t\t\t  \"apply the local changes later by running \\\"git stash pop\\\".\\n\"));\n       \t\telse\n       \t\t\tfprintf(stderr,\n      -\t\t\t\t_(\"%s\\n\"\n     @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n      -\tApplying autostash resulted in conflicts.\n      -\tYour changes are safe in the stash.\n      -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n     -+\tYour local changes are stashed, however, applying it to carry\n     -+\tforward your local changes resulted in conflicts:\n     -+\n     -+\t - You can try resolving them now.  If you resolved them\n     -+\t   successfully, discard the stash entry with \"git stash drop\".\n     -+\n     -+\t - Alternatively you can \"git reset --hard\" if you do not want\n     -+\t   to deal with them right now, and later \"git stash pop\" to\n     -+\t   recover your local changes.\n     ++\tYour local changes are stashed, however applying them\n     ++\tresulted in conflicts.  You can either resolve the conflicts\n     ++\tand then discard the stash with \"git stash drop\", or, if you\n     ++\tdo not want to resolve them now, run \"git reset --hard\" and\n     ++\tapply the local changes later by running \"git stash pop\".\n       \tEOF\n       }\n       \n     @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n      -\tApplying autostash resulted in conflicts.\n      -\tYour changes are safe in the stash.\n      -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n     -+\tYour local changes are stashed, however, applying it to carry\n     -+\tforward your local changes resulted in conflicts:\n     -+\n     -+\t - You can try resolving them now.  If you resolved them\n     -+\t   successfully, discard the stash entry with \"git stash drop\".\n     -+\n     -+\t - Alternatively you can \"git reset --hard\" if you do not want\n     -+\t   to deal with them right now, and later \"git stash pop\" to\n     -+\t   recover your local changes.\n     ++\tYour local changes are stashed, however applying them\n     ++\tresulted in conflicts.  You can either resolve the conflicts\n     ++\tand then discard the stash with \"git stash drop\", or, if you\n     ++\tdo not want to resolve them now, run \"git reset --hard\" and\n     ++\tapply the local changes later by running \"git stash pop\".\n       \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n       \tEOF\n       }\n      \n       ## t/t7201-co.sh ##\n     +@@ t/t7201-co.sh: test_expect_success 'checkout -m with dirty tree' '\n     + \n     + \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n     + \n     +-\tprintf \"M\\t%s\\n\" one >expect.messages &&\n     ++\tprintf \"The following paths have local changes:\\nM\\t%s\\n\" one >expect.messages &&\n     + \ttest_cmp expect.messages messages &&\n     + \n     + \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n      @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n       \ttest_cmp expect two\n       '\n       \n     -+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n     -+\tgit checkout -f main &&\n     -+\tgit reset --hard &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill a b X d e >two &&\n     -+\tgit checkout --merge --conflict=zdiff3 simple &&\n     -+\n     -+\tcat <<-EOF >expect &&\n     -+\ta\n     -+\t<<<<<<< simple\n     -+\tc\n     -+\t||||||| main\n     -+\tb\n     -+\tc\n     -+\td\n     -+\t=======\n     -+\tb\n     -+\tX\n     -+\td\n     -+\t>>>>>>> local\n     -+\te\n     -+\tEOF\n     -+\ttest_cmp expect two\n     -+'\n     -+\n     -+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n     -+\tgit checkout -f main &&\n     -+\tgit reset --hard &&\n     -+\tgit clean -f &&\n     -+\n     -+\ttest_config merge.conflictStyle diff3 &&\n     -+\tfill b d >two &&\n     -+\tgit checkout -m simple &&\n     -+\n     -+\tcat <<-EOF >expect &&\n     -+\t<<<<<<< simple\n     -+\ta\n     -+\tc\n     -+\te\n     -+\t||||||| main\n     -+\ta\n     -+\tb\n     -+\tc\n     -+\td\n     -+\te\n     -+\t=======\n     -+\tb\n     -+\td\n     -+\t>>>>>>> local\n     -+\tEOF\n     -+\ttest_cmp expect two\n     -+'\n     -+\n     -+test_expect_success 'checkout -m skips stash when no conflict' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill 0 x y z >same &&\n     -+\tgit stash list >stash-before &&\n     -+\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\tgit stash list >stash-after &&\n     -+\ttest_cmp stash-before stash-after &&\n     -+\tfill 0 x y z >expect &&\n     -+\ttest_cmp expect same\n     -+'\n     -+\n     -+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill 0 x y z >same &&\n     -+\tgit add same &&\n     -+\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\tfill 0 x y z >expect &&\n     -+\ttest_cmp expect same\n     -+'\n     -+\n     -+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill 1 2 3 4 5 6 7 >one &&\n     -+\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\ttest_grep \"Applied autostash\" actual &&\n     -+\tfill 1 2 3 4 5 6 7 >expect &&\n     -+\ttest_cmp expect one\n     -+'\n     -+\n      +test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n      +\tgit checkout -f main &&\n      +\tgit clean -f &&\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\tgit add same &&\n      +\tfill 1 2 3 4 5 6 7 >one &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n      +\ttest_grep \"Applied autostash\" actual &&\n      +\tfill 0 x y z >expect &&\n      +\ttest_cmp expect same &&\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\ttest_cmp expect one\n      +'\n      +\n     -+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n     ++test_expect_success 'checkout -m creates a recoverable stash on conflict' '\n      +\tgit checkout -f main &&\n      +\tgit clean -f &&\n      +\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\ttest_must_fail git checkout side 2>stderr &&\n      +\ttest_grep \"Your local changes\" stderr &&\n      +\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n      +\ttest_grep \"resulted in conflicts\" actual &&\n      +\ttest_grep \"git stash drop\" actual &&\n     ++\ttest_grep \"git stash pop\" actual &&\n     ++\ttest_grep \"The following paths have local changes\" actual &&\n     ++\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n     ++\tsed /^index/d actual >actual.trimmed &&\n     ++\tcat >expect <<-EOF &&\n     ++\tOn main: autostash while switching to ${SQ}side${SQ}\n     ++\n     ++\tdiff --git a/one b/one\n     ++\t--- a/one\n     ++\t+++ b/one\n     ++\t@@ -3,6 +3,3 @@\n     ++\t 3\n     ++\t 4\n     ++\t 5\n     ++\t-6\n     ++\t-7\n     ++\t-8\n     ++\tEOF\n     ++\ttest_cmp expect actual.trimmed &&\n      +\tgit stash drop &&\n      +\tgit reset --hard\n      +'\n      +\n     -+test_expect_success 'checkout -m produces usable stash on conflict' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     ++test_expect_success 'checkout -m which would overwrite untracked file' '\n     ++\tgit checkout -f --detach main &&\n     ++\ttest_commit another-file &&\n     ++\tgit checkout HEAD^ &&\n     ++\t>another-file.t &&\n      +\tfill 1 2 3 4 5 >one &&\n     -+\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep \"recover your local changes\" actual &&\n     -+\tgit checkout -f main &&\n     -+\tgit stash pop &&\n     -+\tfill 1 2 3 4 5 >expect &&\n     -+\ttest_cmp expect one\n     -+'\n     -+\n     -+test_expect_success 'checkout -m autostash message includes target branch' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill 1 2 3 4 5 >one &&\n     -+\tgit checkout -m side >actual 2>&1 &&\n     -+\tgit stash list >stash-list &&\n     -+\ttest_grep \"autostash while switching to .side.\" stash-list &&\n     -+\tgit stash drop &&\n     -+\tgit checkout -f main &&\n     -+\tgit reset --hard\n     -+'\n     -+\n     -+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill 1 2 3 4 5 >one &&\n     -+\tgit add one &&\n     -+\tgit checkout -m side >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\ttest_grep \"resulted in conflicts\" actual &&\n     -+\ttest_grep \"git stash drop\" actual &&\n     -+\tgit stash drop &&\n     -+\tgit reset --hard\n     -+'\n     -+\n     -+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n     -+\tgit checkout -f main &&\n     -+\tgit reset --hard &&\n     -+\tgit clean -f &&\n     -+\n     -+\tgit checkout -b nonoverlap_base &&\n     -+\tfill a b c d >file &&\n     -+\tgit add file &&\n     -+\tgit commit -m \"add file\" &&\n     -+\n     -+\tgit checkout -b nonoverlap_child &&\n     -+\tfill a b c INSERTED d >file &&\n     -+\tgit commit -a -m \"insert line near end of file\" &&\n     -+\n     -+\tfill DIRTY a b c INSERTED d >file &&\n     -+\n     -+\tgit stash list >stash-before &&\n     -+\tgit checkout -m nonoverlap_base 2>stderr &&\n     -+\ttest_grep \"Applied autostash\" stderr &&\n     -+\ttest_grep ! \"resulted in conflicts\" stderr &&\n     -+\n     -+\tgit stash list >stash-after &&\n     -+\ttest_cmp stash-before stash-after &&\n     -+\n     -+\tfill DIRTY a b c d >expect &&\n     -+\ttest_cmp expect file &&\n     -+\n     -+\tgit checkout -f main &&\n     -+\tgit branch -D nonoverlap_base &&\n     -+\tgit branch -D nonoverlap_child\n     -+'\n     -+\n     -+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n     -+\tgit checkout -f main &&\n     -+\tgit clean -f &&\n     -+\n     -+\tfill 0 x y z >same &&\n     -+\tgit checkout -m -b newbranch >actual 2>&1 &&\n     -+\ttest_grep ! \"Created autostash\" actual &&\n     -+\tfill 0 x y z >expect &&\n     -+\ttest_cmp expect same &&\n     -+\tgit checkout main &&\n     -+\tgit branch -D newbranch\n     ++\ttest_must_fail git checkout -m @{-1} 2>err &&\n     ++\ttest_grep \"would be overwritten by checkout\" err &&\n     ++\ttest_grep \"another-file.t\" err\n      +'\n      +\n       test_expect_success 'switch to another branch while carrying a deletion' '\n     @@ t/t7600-merge.sh: test_expect_success 'merge with conflicted --autostash changes\n       \ttest_when_finished \"test_might_fail git stash drop\" &&\n       \tgit merge --autostash c3 2>err &&\n      -\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n     -+\ttest_grep \"your local changes resulted in conflicts\" err &&\n     ++\ttest_grep \"applying them\" err &&\n     ++\ttest_grep \"resulted in conflicts\" err &&\n       \tgit show HEAD:file >merge-result &&\n       \ttest_cmp result.1-9 merge-result &&\n       \tgit stash show -p >actual &&\n\n-- \ngitgitgadget\n"},{"id":"542277","messageId":"aba8e6a9dc7e1620a46888685bb081f878cc6932.1777065012.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"[PATCH v15 1/5] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-24T21:10:08Z","receivedAt":"2026-04-24T21:10:16Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 28 ++++++++++++++++++++--------\n t/t3903-stash.sh             | 24 ++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 57 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..32dbc97b47 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -591,7 +591,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n }\n \n static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+\t\t\t  int index, int quiet,\n+\t\t\t  const char *label_ours, const char *label_theirs,\n+\t\t\t  const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +645,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t     label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\n@@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info_assert(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tif ((ret = do_apply_stash(prefix, &info, index, quiet)))\n+\tif ((ret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t\t  NULL, NULL, NULL)))\n \t\tprintf_ln(_(\"The stash entry is kept in case \"\n \t\t\t    \"you need it again.\"));\n \telse\n@@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,\n \tstrvec_push(&cp.args, oid_to_hex(&info.b_commit));\n \tret = run_command(&cp);\n \tif (!ret)\n-\t\tret = do_apply_stash(prefix, &info, 1, 0);\n+\t\tret = do_apply_stash(prefix, &info, 1, 0,\n+\t\t\t\t     NULL, NULL, NULL);\n \tif (!ret && info.is_stash_ref)\n \t\tret = do_drop_stash(&info, 0);\n \ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..bdaad22e1f 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -56,6 +56,7 @@ setup_stash() {\n \tgit add other-file &&\n \ttest_tick &&\n \tgit commit -m initial &&\n+\tgit tag initial &&\n \techo 2 >file &&\n \tgit add file &&\n \techo 3 >file &&\n@@ -1790,4 +1791,27 @@ test_expect_success 'stash.index=false overridden by --index' '\n \ttest_cmp expect file\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit reset --hard initial &&\n+\ttest_commit label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\ttest_grep \"^<<<<<<< UP\" conflict-file &&\n+\ttest_grep \"^||||||| Stash base\" conflict-file &&\n+\ttest_grep \"^>>>>>>> STASH\" conflict-file\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit reset --hard initial &&\n+\ttest_commit empty-label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit empty-label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\ttest_grep \"^<<<<<<<$\" conflict-file &&\n+\ttest_grep \"^>>>>>>>$\" conflict-file\n+'\n+\n test_done\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"542278","messageId":"89e0bfa80326d20d11355ed2e73de0907e5c9e41.1777065012.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"[PATCH v15 2/5] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-24T21:10:09Z","receivedAt":"2026-04-24T21:10:18Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/merge.c |  6 ++++--\n sequencer.c     | 17 +++++++++++------\n sequencer.h     |  3 ++-\n 3 files changed, 17 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 2cbce56f8d..3ebe190ef1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1672,7 +1672,8 @@ int cmd_merge(int argc,\n \t\t}\n \n \t\tif (autostash)\n-\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t     NULL, false);\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1764,7 +1765,8 @@ int cmd_merge(int argc,\n \t\tdie_ff_impossible();\n \n \tif (autostash)\n-\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t     NULL, false);\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..ff5258f481 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      const char *message,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n+\t\t\t     \"stash\", \"create\",\n+\t\t\t     message ? message : \"autostash\", NULL);\n \t\tstash.git_cmd = 1;\n \t\tstash.no_stdin = 1;\n \t\tstrbuf_reset(&buf);\n@@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4718,13 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n }\n \n-void create_autostash_ref(struct repository *r, const char *refname)\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..02d2d9db06 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -229,7 +229,8 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct object_id *new_head);\n \n void create_autostash(struct repository *r, const char *path);\n-void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"542279","messageId":"a428ce73281dd288f0c4f44c4f92c03129556b7f.1777065012.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"[PATCH v15 3/5] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-24T21:10:10Z","receivedAt":"2026-04-24T21:10:19Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, label_base, and stash_msg parameters to\napply_autostash_ref() and the autostash apply machinery so callers can\npass custom conflict marker labels through to\n\"git stash apply --label-ours/--label-theirs/--label-base\", as well as\na custom stash message for \"git stash store -m\".\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/commit.c |  3 ++-\n builtin/merge.c  |  9 ++++++---\n sequencer.c      | 38 +++++++++++++++++++++++++++++---------\n sequencer.h      |  4 +++-\n 4 files changed, 40 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex a3e52ac9ca..28f6174503 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1979,7 +1979,8 @@ int cmd_commit(int argc,\n \t\t\t\t     &oid, flags);\n \t}\n \n-\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t    NULL, NULL, NULL, NULL);\n \n cleanup:\n \tfree_commit_extra_headers(extra);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 3ebe190ef1..aacf8c524e 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -537,7 +537,8 @@ static void finish(struct commit *head_commit,\n \trun_hooks_l(the_repository, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tif (new_head)\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \tstrbuf_release(&reflog_message);\n }\n \n@@ -1678,7 +1679,8 @@ int cmd_merge(int argc,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n \t\t\t\t\t  overwrite_ignore)) {\n-\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\t\tret = 1;\n \t\t\tgoto done;\n \t\t}\n@@ -1851,7 +1853,8 @@ int cmd_merge(int argc,\n \t\telse\n \t\t\tfprintf(stderr, _(\"Merge with strategy %s failed.\\n\"),\n \t\t\t\tuse_strategies[0]->name);\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\tret = 2;\n \t\tgoto done;\n \t} else if (best_strategy == wt_strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex ff5258f481..7c0376d9e4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4727,7 +4727,10 @@ void create_autostash_ref(struct repository *r, const char *refname,\n \tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4738,6 +4741,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4751,7 +4760,7 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tstrvec_push(&store.args, \"stash\");\n \t\tstrvec_push(&store.args, \"store\");\n \t\tstrvec_push(&store.args, \"-m\");\n-\t\tstrvec_push(&store.args, \"autostash\");\n+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n \t\tstrvec_push(&store.args, \"-q\");\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n@@ -4782,7 +4791,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4801,11 +4811,14 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4821,7 +4834,9 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base,\n+\t\t\t\t       stash_msg);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4831,12 +4846,17 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n-int apply_autostash_ref(struct repository *r, const char *refname)\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n+\t\t\t\t\tstash_msg);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 02d2d9db06..3164bd437d 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -235,7 +235,9 @@ int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"542280","messageId":"f35842408527a8c69f9936cdb127fb35efe2509c.1777065012.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"[PATCH v15 4/5] checkout: rollback lock on early returns in merge_working_tree","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-24T21:10:11Z","receivedAt":"2026-04-24T21:10:21Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nmerge_working_tree() acquires the index lock via\nrepo_hold_locked_index() but several early return paths exit\nwithout calling rollback_lock_file(), leaving the lock held.\nWhile this is currently harmless because the process exits soon\nafter, it becomes a problem if the function is ever called more\nthan once in the same process.\n\nAdd rollback_lock_file() calls to all early return paths.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/checkout.c | 29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..c80c62b37b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -857,15 +864,19 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n \t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n \n-\t\t\tif (!opts->merge)\n+\t\t\tif (!opts->merge) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \n \t\t\t/*\n \t\t\t * Without old_branch_info->commit, the below is the same as\n \t\t\t * the two-tree unpack we already tried and failed.\n \t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n+\t\t\tif (!old_branch_info->commit) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \t\t\told_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\t\told_branch_info->commit);\n \n@@ -897,8 +908,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\n \t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t\to.ancestor = old_branch_info->name;\n \t\t\tif (!old_branch_info->name) {\n \t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n@@ -920,8 +933,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t writeout_error, new_branch_info);\n \t\t\tstrbuf_release(&o.obuf);\n \t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t}\n \t}\n \n-- \ngitgitgadget\n\n"},{"id":"542281","messageId":"96b14db827e51966aceb6513f7d16ed526274ec2.1777065012.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"[PATCH v15 5/5] checkout -m: autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-24T21:10:12Z","receivedAt":"2026-04-24T21:10:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", the attempted merge\nof local modifications may cause conflicts with the changes made on\nthe other branch, which the user may not want to (or may not be able\nto) resolve right now.  Because there is no easy way to recover from\nthis situation, we discouraged users from using \"checkout -m\" unless\nthey are certain their changes are trivial and within their ability\nto resolve conflicts.\n\nTeach the -m flow to create a temporary stash before switching and\nreapply it after.  On success, the stash is silently applied and\nthe list of locally modified paths is shown, same as a successful\n\"git checkout\" without \"-m\".\n\nIf reapplying causes conflicts, the stash is kept and the user is\ntold they can resolve and run \"git stash drop\", or run \"git reset\n--hard\" and later \"git stash pop\" to recover their changes.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  58 ++++++------\n Documentation/git-switch.adoc   |  33 +++----\n builtin/checkout.c              | 160 ++++++++++++++------------------\n sequencer.c                     |  14 ++-\n t/t3420-rebase-autostash.sh     |  16 ++--\n t/t7201-co.sh                   |  61 +++++++++++-\n t/t7600-merge.sh                |   3 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 9 files changed, 211 insertions(+), 147 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..70dd211ee3 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,39 +577,44 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is in effect and the locally\n+modified files overlap with files that need to be updated by the\n+branch switch, the changes are stashed and reapplied after the\n+switch.  If this process results in conflicts, a stash entry is saved\n+and made available in `git stash list`:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n+Your local changes are stashed, however, applying it to carry\n+forward your local changes resulted in conflicts:\n \n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n+ - You can try resolving them now.  If you resolved them\n+   successfully, discard the stash entry with \"git stash drop\".\n \n+ - Alternatively you can \"git reset --hard\" if you do not want\n+   to deal with them right now, and later \"git stash pop\" to\n+   recover your local changes.\n ------------\n-$ edit frotz\n-$ git add frotz\n-------------\n+\n+You can try resolving the conflicts now.  Edit the conflicting files\n+and mark them resolved with `git add` as usual, then run `git stash\n+drop` to discard the stash entry.  Alternatively, you can clear the\n+working tree with `git reset --hard` and recover your local changes\n+later with `git stash pop`.\n \n CONFIGURATION\n -------------\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..ee58a4d0fd 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,15 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which would carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Switched to branch 'mytopic'\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c80c62b37b..d02183b245 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -99,6 +99,8 @@ struct checkout_opts {\n \t.auto_advance = 1, \\\n }\n \n+#define MERGE_WORKING_TREE_UNPACK_FAILED (-2)\n+\n struct branch_info {\n \tchar *name; /* The short name used */\n \tchar *path; /* The full name of a real branch */\n@@ -755,7 +757,7 @@ static void setup_branch_path(struct branch_info *branch)\n \n static void init_topts(struct unpack_trees_options *topts, int merge,\n \t\t       int show_progress, int overwrite_ignore,\n-\t\t       struct commit *old_commit)\n+\t\t       struct commit *old_commit, bool show_unpack_errors)\n {\n \tmemset(topts, 0, sizeof(*topts));\n \ttopts->head_idx = -1;\n@@ -767,7 +769,7 @@ static void init_topts(struct unpack_trees_options *topts, int merge,\n \ttopts->initial_checkout = is_index_unborn(the_repository->index);\n \ttopts->update = 1;\n \ttopts->merge = 1;\n-\ttopts->quiet = merge && old_commit;\n+\ttopts->quiet = merge && old_commit && !show_unpack_errors;\n \ttopts->verbose_update = show_progress;\n \ttopts->fn = twoway_merge;\n \ttopts->preserve_ignored = !overwrite_ignore;\n@@ -776,7 +778,8 @@ static void init_topts(struct unpack_trees_options *topts, int merge,\n static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t      struct branch_info *old_branch_info,\n \t\t\t      struct branch_info *new_branch_info,\n-\t\t\t      int *writeout_error)\n+\t\t\t      int *writeout_error,\n+\t\t\t      bool show_unpack_errors)\n {\n \tint ret;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -827,7 +830,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \n \t\t/* 2-way merge to the new branch */\n \t\tinit_topts(&topts, opts->merge, opts->show_progress,\n-\t\t\t   opts->overwrite_ignore, old_branch_info->commit);\n+\t\t\t   opts->overwrite_ignore, old_branch_info->commit,\n+\t\t\t   show_unpack_errors);\n \t\tinit_checkout_metadata(&topts.meta, new_branch_info->refname,\n \t\t\t\t       new_branch_info->commit ?\n \t\t\t\t       &new_branch_info->commit->object.oid :\n@@ -853,90 +857,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn MERGE_WORKING_TREE_UNPACK_FAILED;\n \t\t}\n \t}\n \n@@ -1181,6 +1103,10 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tstruct strbuf autostash_msg = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1218,11 +1144,49 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name) {\n+\t\tstash_label_base = old_branch_info.name;\n+\t} else if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n-\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n+\t\t\t\t\t &writeout_error, false);\n+\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n+\t\t\tstrbuf_addf(&autostash_msg,\n+\t\t\t\t    \"autostash while switching to '%s'\",\n+\t\t\t\t    new_branch_info->name);\n+\t\t\tcreate_autostash_ref(the_repository,\n+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t     autostash_msg.buf, true);\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n+\t\t\t\t\t\t &writeout_error, true);\n+\t\t}\n+\t\tif (created_autostash) {\n+\t\t\tif (opts->conflict_style >= 0) {\n+\t\t\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\t\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\t\t\tgit_config_push_parameter(cfg.buf);\n+\t\t\t\tstrbuf_release(&cfg);\n+\t\t\t}\n+\t\t\tapply_autostash_ref(the_repository,\n+\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t    new_branch_info->name,\n+\t\t\t\t\t    \"local\",\n+\t\t\t\t\t    stash_label_base,\n+\t\t\t\t\t    autostash_msg.buf);\n+\t\t}\n \t\tif (ret) {\n \t\t\tbranch_info_release(&old_branch_info);\n-\t\t\treturn ret;\n+\t\t\tstrbuf_release(&old_commit_shortname);\n+\t\t\tstrbuf_release(&autostash_msg);\n+\t\t\treturn ret < 0 ? 1 : ret;\n \t\t}\n \t}\n \n@@ -1231,8 +1195,22 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (created_autostash) {\n+\t\tdiscard_index(the_repository->index);\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\n+\t\tif (!opts->quiet && new_branch_info->commit) {\n+\t\t\tprintf(_(\"The following paths have local changes:\\n\"));\n+\t\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t\t   &opts->diff_options);\n+\t\t}\n+\t}\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n+\tstrbuf_release(&autostash_msg);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 7c0376d9e4..746f85a442 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4765,15 +4765,19 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however applying them\\n\"\n+\t\t\t\t  \"resulted in conflicts.  You can either resolve the conflicts\\n\"\n+\t\t\t\t  \"and then discard the stash with \\\"git stash drop\\\", or, if you\\n\"\n+\t\t\t\t  \"do not want to resolve them now, run \\\"git reset --hard\\\" and\\n\"\n+\t\t\t\t  \"apply the local changes later by running \\\"git stash pop\\\".\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..f0bbc476ff 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,22 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however applying them\n+\tresulted in conflicts.  You can either resolve the conflicts\n+\tand then discard the stash with \"git stash drop\", or, if you\n+\tdo not want to resolve them now, run \"git reset --hard\" and\n+\tapply the local changes later by running \"git stash pop\".\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however applying them\n+\tresulted in conflicts.  You can either resolve the conflicts\n+\tand then discard the stash with \"git stash drop\", or, if you\n+\tdo not want to resolve them now, run \"git reset --hard\" and\n+\tapply the local changes later by running \"git stash pop\".\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..b3293ead8d 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -102,7 +102,7 @@ test_expect_success 'checkout -m with dirty tree' '\n \n \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n \n-\tprintf \"M\\t%s\\n\" one >expect.messages &&\n+\tprintf \"The following paths have local changes:\\nM\\t%s\\n\" one >expect.messages &&\n \ttest_cmp expect.messages messages &&\n \n \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n@@ -210,6 +210,65 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m creates a recoverable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\ttest_grep \"git stash pop\" actual &&\n+\ttest_grep \"The following paths have local changes\" actual &&\n+\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n+\tsed /^index/d actual >actual.trimmed &&\n+\tcat >expect <<-EOF &&\n+\tOn main: autostash while switching to ${SQ}side${SQ}\n+\n+\tdiff --git a/one b/one\n+\t--- a/one\n+\t+++ b/one\n+\t@@ -3,6 +3,3 @@\n+\t 3\n+\t 4\n+\t 5\n+\t-6\n+\t-7\n+\t-8\n+\tEOF\n+\ttest_cmp expect actual.trimmed &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m which would overwrite untracked file' '\n+\tgit checkout -f --detach main &&\n+\ttest_commit another-file &&\n+\tgit checkout HEAD^ &&\n+\t>another-file.t &&\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout -m @{-1} 2>err &&\n+\ttest_grep \"would be overwritten by checkout\" err &&\n+\ttest_grep \"another-file.t\" err\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..f877d9a433 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,8 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"applying them\" err &&\n+\ttest_grep \"resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"542418","messageId":"6d60573f-a02d-4aea-b891-6dd52e2d7048@gmail.com","threadId":"65220","inReplyTo":"aba8e6a9dc7e1620a46888685bb081f878cc6932.1777065012.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v15 1/5] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-28T09:32:10Z","receivedAt":"2026-04-28T09:32:14Z","isPatch":true,"body":"Hi Harald\n\nOn 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Allow callers of \"git stash apply\" to pass custom labels for conflict\n> markers instead of the default \"Updated upstream\" and \"Stashed changes\".\n> Document the new options and add a test.\n\nThis all looks good, just one small comment below\n\n> -\to.branch1 = \"Updated upstream\";\n> -\to.branch2 = \"Stashed changes\";\n> -\to.ancestor = \"Stash base\";\n> +\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n> +\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n> +\to.ancestor = label_base ? label_base : \"Stash base\";\n\nThis uses the existing label which is sensible, but I wonder if \"Stash \nHEAD\" would be a better choice as the merge base is always HEAD commit \nthat the stash is based on.\n\nWe can always change that later\n\nThanks\n\nPhillip\n\n>   \n>   \tif (oideq(&info->b_tree, &c_tree))\n>   \t\to.branch1 = \"Version stash was based on\";\n> @@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n>   \tint ret = -1;\n>   \tint quiet = 0;\n>   \tint index = use_index;\n> +\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n>   \tstruct stash_info info = STASH_INFO_INIT;\n>   \tstruct option options[] = {\n>   \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n>   \t\tOPT_BOOL(0, \"index\", &index,\n>   \t\t\t N_(\"attempt to recreate the index\")),\n> +\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n> +\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n> +\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n> +\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n> +\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n> +\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n>   \t\tOPT_END()\n>   \t};\n>   \n> @@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n>   \tif (get_stash_info(&info, argc, argv))\n>   \t\tgoto cleanup;\n>   \n> -\tret = do_apply_stash(prefix, &info, index, quiet);\n> +\tret = do_apply_stash(prefix, &info, index, quiet,\n> +\t\t\t     label_ours, label_theirs, label_base);\n>   cleanup:\n>   \tfree_stash_info(&info);\n>   \treturn ret;\n> @@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,\n>   \tif (get_stash_info_assert(&info, argc, argv))\n>   \t\tgoto cleanup;\n>   \n> -\tif ((ret = do_apply_stash(prefix, &info, index, quiet)))\n> +\tif ((ret = do_apply_stash(prefix, &info, index, quiet,\n> +\t\t\t\t  NULL, NULL, NULL)))\n>   \t\tprintf_ln(_(\"The stash entry is kept in case \"\n>   \t\t\t    \"you need it again.\"));\n>   \telse\n> @@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,\n>   \tstrvec_push(&cp.args, oid_to_hex(&info.b_commit));\n>   \tret = run_command(&cp);\n>   \tif (!ret)\n> -\t\tret = do_apply_stash(prefix, &info, 1, 0);\n> +\t\tret = do_apply_stash(prefix, &info, 1, 0,\n> +\t\t\t\t     NULL, NULL, NULL);\n>   \tif (!ret && info.is_stash_ref)\n>   \t\tret = do_drop_stash(&info, 0);\n>   \n> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\n> index 70879941c2..bdaad22e1f 100755\n> --- a/t/t3903-stash.sh\n> +++ b/t/t3903-stash.sh\n> @@ -56,6 +56,7 @@ setup_stash() {\n>   \tgit add other-file &&\n>   \ttest_tick &&\n>   \tgit commit -m initial &&\n> +\tgit tag initial &&\n>   \techo 2 >file &&\n>   \tgit add file &&\n>   \techo 3 >file &&\n> @@ -1790,4 +1791,27 @@ test_expect_success 'stash.index=false overridden by --index' '\n>   \ttest_cmp expect file\n>   '\n>   \n> +test_expect_success 'apply with custom conflict labels' '\n> +\tgit reset --hard initial &&\n> +\ttest_commit label-base conflict-file base-content &&\n> +\techo stashed >conflict-file &&\n> +\tgit stash push -m \"stashed\" &&\n> +\ttest_commit label-upstream conflict-file upstream-content &&\n> +\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n> +\ttest_grep \"^<<<<<<< UP\" conflict-file &&\n> +\ttest_grep \"^||||||| Stash base\" conflict-file &&\n> +\ttest_grep \"^>>>>>>> STASH\" conflict-file\n> +'\n> +\n> +test_expect_success 'apply with empty conflict labels' '\n> +\tgit reset --hard initial &&\n> +\ttest_commit empty-label-base conflict-file base-content &&\n> +\techo stashed >conflict-file &&\n> +\tgit stash push -m \"stashed\" &&\n> +\ttest_commit empty-label-upstream conflict-file upstream-content &&\n> +\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n> +\ttest_grep \"^<<<<<<<$\" conflict-file &&\n> +\ttest_grep \"^>>>>>>>$\" conflict-file\n> +'\n> +\n>   test_done\n> diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\n> index 29dad98c49..659ad4ec97 100644\n> --- a/xdiff/xmerge.c\n> +++ b/xdiff/xmerge.c\n> @@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n>   \t\t\t      int size, int i, int style,\n>   \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n>   {\n> -\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n> -\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n> -\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n> +\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n> +\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n> +\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n>   \tint needs_cr = is_cr_needed(xe1, xe2, m);\n>   \n>   \tif (marker_size <= 0)\n\n"},{"id":"542419","messageId":"a3197c7d-4a19-4cda-832c-b6e67214e7dc@gmail.com","threadId":"65220","inReplyTo":"89e0bfa80326d20d11355ed2e73de0907e5c9e41.1777065012.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v15 2/5] sequencer: allow create_autostash to run silently","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-28T09:32:27Z","receivedAt":"2026-04-28T09:32:31Z","isPatch":true,"body":"Hi Harald\n\nThis looks good\n\nThanks\n\nPhillip\n\nOn 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Add a silent parameter to create_autostash_internal and introduce\n> create_autostash_ref_silent so that callers can create an autostash\n> without printing the \"Created autostash\" message.\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>   builtin/merge.c |  6 ++++--\n>   sequencer.c     | 17 +++++++++++------\n>   sequencer.h     |  3 ++-\n>   3 files changed, 17 insertions(+), 9 deletions(-)\n> \n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index 2cbce56f8d..3ebe190ef1 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -1672,7 +1672,8 @@ int cmd_merge(int argc,\n>   \t\t}\n>   \n>   \t\tif (autostash)\n> -\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n> +\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n> +\t\t\t\t\t     NULL, false);\n>   \t\tif (checkout_fast_forward(the_repository,\n>   \t\t\t\t\t  &head_commit->object.oid,\n>   \t\t\t\t\t  &commit->object.oid,\n> @@ -1764,7 +1765,8 @@ int cmd_merge(int argc,\n>   \t\tdie_ff_impossible();\n>   \n>   \tif (autostash)\n> -\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n> +\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n> +\t\t\t\t     NULL, false);\n>   \n>   \t/* We are going to make a new commit. */\n>   \tgit_committer_info(IDENT_STRICT);\n> diff --git a/sequencer.c b/sequencer.c\n> index b7d8dca47f..ff5258f481 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n>   \n>   static void create_autostash_internal(struct repository *r,\n>   \t\t\t\t      const char *path,\n> -\t\t\t\t      const char *refname)\n> +\t\t\t\t      const char *refname,\n> +\t\t\t\t      const char *message,\n> +\t\t\t\t      bool silent)\n>   {\n>   \tstruct strbuf buf = STRBUF_INIT;\n>   \tstruct lock_file lock_file = LOCK_INIT;\n> @@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n>   \t\tstruct object_id oid;\n>   \n>   \t\tstrvec_pushl(&stash.args,\n> -\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n> +\t\t\t     \"stash\", \"create\",\n> +\t\t\t     message ? message : \"autostash\", NULL);\n>   \t\tstash.git_cmd = 1;\n>   \t\tstash.no_stdin = 1;\n>   \t\tstrbuf_reset(&buf);\n> @@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n>   \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n>   \t\t}\n>   \n> -\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n> +\t\tif (!silent)\n> +\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n>   \t\tif (reset_head(r, &ropts) < 0)\n>   \t\t\tdie(_(\"could not reset --hard\"));\n>   \t\tdiscard_index(r->index);\n> @@ -4714,12 +4718,13 @@ static void create_autostash_internal(struct repository *r,\n>   \n>   void create_autostash(struct repository *r, const char *path)\n>   {\n> -\tcreate_autostash_internal(r, path, NULL);\n> +\tcreate_autostash_internal(r, path, NULL, NULL, false);\n>   }\n>   \n> -void create_autostash_ref(struct repository *r, const char *refname)\n> +void create_autostash_ref(struct repository *r, const char *refname,\n> +\t\t\t  const char *message, bool silent)\n>   {\n> -\tcreate_autostash_internal(r, NULL, refname);\n> +\tcreate_autostash_internal(r, NULL, refname, message, silent);\n>   }\n>   \n>   static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n> diff --git a/sequencer.h b/sequencer.h\n> index a6fa670c7c..02d2d9db06 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -229,7 +229,8 @@ void commit_post_rewrite(struct repository *r,\n>   \t\t\t const struct object_id *new_head);\n>   \n>   void create_autostash(struct repository *r, const char *path);\n> -void create_autostash_ref(struct repository *r, const char *refname);\n> +void create_autostash_ref(struct repository *r, const char *refname,\n> +\t\t\t  const char *message, bool silent);\n>   int save_autostash(const char *path);\n>   int save_autostash_ref(struct repository *r, const char *refname);\n>   int apply_autostash(const char *path);\n\n"},{"id":"542420","messageId":"66e3b1f0-e00d-4b9b-8ee7-ca71444cf56d@gmail.com","threadId":"65220","inReplyTo":"a428ce73281dd288f0c4f44c4f92c03129556b7f.1777065012.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v15 3/5] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-28T09:33:38Z","receivedAt":"2026-04-28T09:33:42Z","isPatch":true,"body":"Hi Harald\n\nOn 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Add label_ours, label_theirs, label_base, and stash_msg parameters to\n> apply_autostash_ref() and the autostash apply machinery so callers can\n> pass custom conflict marker labels through to\n> \"git stash apply --label-ours/--label-theirs/--label-base\", as well as\n> a custom stash message for \"git stash store -m\".\n\nI said before that I was confused as to why we need to pass a message \nwhen all we're doing is saving an existing stash and the stash already \nhas the message we want. As that didn't get any response I've had a more \ndetailed look. The message here is used in the reflog (which is what \n\"git stash list\" shows) and it appears that the reason for passing a \nmessage when storing the stash is because \"git stash store\" is not smart \nenough to reuse the subject from the stash commit message by default. It \nwould be nice to improve that rather than having to pass a message here. \n\"git rebase --autostash\" and \"git merge --autostash\" do not have the \nluxury of calling \"git stash store\" from the same process as \"git stash \ncreate\" and so to improve the autostash message for those commands we'd \nneed to generate it twice which seems like an unnecessary complication.\n\nOne problem with that approach in the context of this series is that the \nstash commit message begins \"On <branch we're switching from>: \" rather \nthan \"On <branch we're switching to>: \" but that means this commit is \njust hiding the fact that commit message is wrong when we create the stash.\n\nIt may well be that fixing all that turns out to be a lot of work as it \nwould mean modifying do_create_stash() to allow the branch name to be \noverridden and modifying store_stash() to use the commit subject as the \nreflog message in which case we should leave that for a future series.\n\nThanks\n\nPhillip\n\n"},{"id":"542421","messageId":"04b49202-d1bb-45a1-ae4d-4a87a5822ba1@gmail.com","threadId":"65220","inReplyTo":"f35842408527a8c69f9936cdb127fb35efe2509c.1777065012.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v15 4/5] checkout: rollback lock on early returns in merge_working_tree","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-28T09:33:47Z","receivedAt":"2026-04-28T09:33:51Z","isPatch":true,"body":"Hi Harald\n\nOn 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> merge_working_tree() acquires the index lock via\n> repo_hold_locked_index() but several early return paths exit\n> without calling rollback_lock_file(), leaving the lock held.\n> While this is currently harmless because the process exits soon\n> after, it becomes a problem if the function is ever called more\n> than once in the same process.\n> \n> Add rollback_lock_file() calls to all early return paths.\n\nThanks for splitting this out, it looks good\n\nPhillip\n\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>   builtin/checkout.c | 29 ++++++++++++++++++++++-------\n>   1 file changed, 22 insertions(+), 7 deletions(-)\n> \n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index e031e61886..c80c62b37b 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \tstruct tree *new_tree;\n>   \n>   \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n> -\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n> +\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n> +\t\trollback_lock_file(&lock_file);\n>   \t\treturn error(_(\"index file corrupt\"));\n> +\t}\n>   \n>   \tresolve_undo_clear_index(the_repository->index);\n>   \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n> @@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t} else {\n>   \t\tnew_tree = repo_get_commit_tree(the_repository,\n>   \t\t\t\t\t\tnew_branch_info->commit);\n> -\t\tif (!new_tree)\n> +\t\tif (!new_tree) {\n> +\t\t\trollback_lock_file(&lock_file);\n>   \t\t\treturn error(_(\"unable to read tree (%s)\"),\n>   \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n> +\t\t}\n>   \t}\n>   \tif (opts->discard_changes) {\n>   \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n> -\t\tif (ret)\n> +\t\tif (ret) {\n> +\t\t\trollback_lock_file(&lock_file);\n>   \t\t\treturn ret;\n> +\t\t}\n>   \t} else {\n>   \t\tstruct tree_desc trees[2];\n>   \t\tstruct tree *tree;\n> @@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n>   \n>   \t\tif (unmerged_index(the_repository->index)) {\n> +\t\t\trollback_lock_file(&lock_file);\n>   \t\t\terror(_(\"you need to resolve your current index first\"));\n>   \t\t\treturn 1;\n>   \t\t}\n> @@ -857,15 +864,19 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\t\tstruct strbuf sb = STRBUF_INIT;\n>   \t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n>   \n> -\t\t\tif (!opts->merge)\n> +\t\t\tif (!opts->merge) {\n> +\t\t\t\trollback_lock_file(&lock_file);\n>   \t\t\t\treturn 1;\n> +\t\t\t}\n>   \n>   \t\t\t/*\n>   \t\t\t * Without old_branch_info->commit, the below is the same as\n>   \t\t\t * the two-tree unpack we already tried and failed.\n>   \t\t\t */\n> -\t\t\tif (!old_branch_info->commit)\n> +\t\t\tif (!old_branch_info->commit) {\n> +\t\t\t\trollback_lock_file(&lock_file);\n>   \t\t\t\treturn 1;\n> +\t\t\t}\n>   \t\t\told_tree = repo_get_commit_tree(the_repository,\n>   \t\t\t\t\t\t\told_branch_info->commit);\n>   \n> @@ -897,8 +908,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\t\tret = reset_tree(new_tree,\n>   \t\t\t\t\t opts, 1,\n>   \t\t\t\t\t writeout_error, new_branch_info);\n> -\t\t\tif (ret)\n> +\t\t\tif (ret) {\n> +\t\t\t\trollback_lock_file(&lock_file);\n>   \t\t\t\treturn ret;\n> +\t\t\t}\n>   \t\t\to.ancestor = old_branch_info->name;\n>   \t\t\tif (!old_branch_info->name) {\n>   \t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n> @@ -920,8 +933,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\t\t\t\t writeout_error, new_branch_info);\n>   \t\t\tstrbuf_release(&o.obuf);\n>   \t\t\tstrbuf_release(&old_commit_shortname);\n> -\t\t\tif (ret)\n> +\t\t\tif (ret) {\n> +\t\t\t\trollback_lock_file(&lock_file);\n>   \t\t\t\treturn ret;\n> +\t\t\t}\n>   \t\t}\n>   \t}\n>   \n\n"},{"id":"542422","messageId":"88a89e06-5223-4a6f-8f9e-66e72b632ee2@gmail.com","threadId":"65220","inReplyTo":"96b14db827e51966aceb6513f7d16ed526274ec2.1777065012.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v15 5/5] checkout -m: autostash when switching branches","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-28T09:35:29Z","receivedAt":"2026-04-28T09:35:34Z","isPatch":true,"body":"Hi Harald\n\nOn 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> When switching branches with \"git checkout -m\", the attempted merge\n> of local modifications may cause conflicts with the changes made on\n> the other branch, which the user may not want to (or may not be able\n> to) resolve right now.  Because there is no easy way to recover from\n> this situation, we discouraged users from using \"checkout -m\" unless\n> they are certain their changes are trivial and within their ability\n> to resolve conflicts.\n> \n> Teach the -m flow to create a temporary stash before switching and\n> reapply it after.  On success, the stash is silently applied and\n> the list of locally modified paths is shown, same as a successful\n> \"git checkout\" without \"-m\".\n> \n> If reapplying causes conflicts, the stash is kept and the user is\n> told they can resolve and run \"git stash drop\", or run \"git reset\n> --hard\" and later \"git stash pop\" to recover their changes.\n\nThis is looking good, there are just a few small issues. Hopefully the \nnext iteration will be the last.\n\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>   Documentation/git-checkout.adoc |  58 ++++++------\n>   Documentation/git-switch.adoc   |  33 +++----\n>   builtin/checkout.c              | 160 ++++++++++++++------------------\n>   sequencer.c                     |  14 ++-\n>   t/t3420-rebase-autostash.sh     |  16 ++--\n>   t/t7201-co.sh                   |  61 +++++++++++-\n>   t/t7600-merge.sh                |   3 +-\n>   xdiff-interface.c               |  12 +++\n>   xdiff-interface.h               |   1 +\n>   9 files changed, 211 insertions(+), 147 deletions(-)\n> \n> diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\n> index 43ccf47cf6..70dd211ee3 100644\n> --- a/Documentation/git-checkout.adoc\n> +++ b/Documentation/git-checkout.adoc\n> @@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n>   \tare different between the current branch and the branch to\n>   \twhich you are switching, the command refuses to switch\n>   \tbranches in order to preserve your modifications in context.\n> -\tHowever, with this option, a three-way merge between the current\n> -\tbranch, your working tree contents, and the new branch\n> -\tis done, and you will be on the new branch.\n> -+\n> -When a merge conflict happens, the index entries for conflicting\n> -paths are left unmerged, and you need to resolve the conflicts\n> -and mark the resolved paths with `git add` (or `git rm` if the merge\n> -should result in deletion of the path).\n> +\tWith this option, the conflicting local changes are\n> +\tautomatically stashed before the switch and reapplied\n> +\tafterwards.  If the local changes do not overlap with the\n> +\tdifferences between branches, the switch proceeds without\n> +\tstashing.  If reapplying the stash results in conflicts, the\n> +\tentry is saved to the stash list.  Resolve the conflicts\n> +\tand run `git stash drop` when done, or clear the working\n> +\ttree (e.g. with `git reset --hard`) before running `git stash\n> +\tpop` later to re-apply your changes.\n>   +\n>   When checking out paths from the index, this option lets you recreate\n>   the conflicted merge in the specified paths.  This option cannot be\n>   used when checking out paths from a tree-ish.\n> -+\n> -When switching branches with `--merge`, staged changes may be lost.\n>   \n>   `--conflict=<style>`::\n>   \tThe same as `--merge` option above, but changes the way the\n> @@ -578,39 +577,44 @@ $ git checkout mytopic\n>   error: You have local changes to 'frotz'; not switching branches.\n>   ------------\n>   \n> -You can give the `-m` flag to the command, which would try a\n> -three-way merge:\n> +You can give the `-m` flag to the command, which would carry your local\n\ns/would/will/ or s/would carry/carries/\n\n> +changes to the new branch:\n>   \n>   ------------\n>   $ git checkout -m mytopic\n> -Auto-merging frotz\n> +Switched to branch 'mytopic'\n>   ------------\n>   \n> -After this three-way merge, the local modifications are _not_\n> +After the switch, the local modifications are reapplied and are _not_\n>   registered in your index file, \n\nWe don't test this but we do test that \"git stash apply\" does not update \nthe index so we should be fine.\n\n> so `git diff` would show you what\n>   changes you made since the tip of the new branch.\n>   \n>   === 3. Merge conflict\n>   \n> -When a merge conflict happens during switching branches with\n> -the `-m` option, you would see something like this:\n> +When the `--merge` (`-m`) option is in effect and the locally\n> +modified files overlap with files  that need to be updated by the\n> +branch switch,\n\nIt is the changes in the files overlapping that causes the merge \nconflict, not the files overlapping\n\n\tWhen the `--merge` (`-m`) option is given and the local changes\n\toverlap with the changes in the branch we're switching to,\n\n> the changes are stashed and reapplied after the\n> +switch.  If this process results in conflicts, a stash entry is saved\n\ns/a/the/\n\n> +and made available in `git stash list`:\n\nI'd drop this line and say instead \"a message is printed\"\n\n>   ------------\n>   $ git checkout -m mytopic\n> -Auto-merging frotz\n> -ERROR: Merge conflict in frotz\n> -fatal: merge program failed\n> -------------\n> +Your local changes are stashed, however, applying it to carry\n> +forward your local changes resulted in conflicts:\n>   \n> -At this point, `git diff` shows the changes cleanly merged as in\n> -the previous example, as well as the changes in the conflicted\n> -files.  Edit and resolve the conflict and mark it resolved with\n> -`git add` as usual:\n> + - You can try resolving them now.  If you resolved them\n> +   successfully, discard the stash entry with \"git stash drop\".\n>   \n> + - Alternatively you can \"git reset --hard\" if you do not want\n> +   to deal with them right now, and later \"git stash pop\" to\n> +   recover your local changes.\n\nThis needs updating to match the new conflict advice.\n\n>   ------------\n> -$ edit frotz\n> -$ git add frotz\n> -------------\n> +\n> +You can try resolving the conflicts now.  Edit the conflicting files\n> +and mark them resolved with `git add` as usual, then run `git stash\n> +drop` to discard the stash entry.  Alternatively, you can clear the\n> +working tree with `git reset --hard` and recover your local changes\n> +later with `git stash pop`.\n\nIs this documentation, or program output?\n\nIf you've not done so already it would be well worth checking the \ngenerated git-checkout.html and the man page\n\n>   CONFIGURATION\n>   -------------\n> diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\n> index 87707e9265..ee58a4d0fd 100644\n> --- a/Documentation/git-switch.adoc\n> +++ b/Documentation/git-switch.adoc\n> @@ -123,18 +123,19 @@ variable.\n>   \n>   `-m`::\n>   `--merge`::\n> -\tIf you have local modifications to one or more files that are\n> -\tdifferent between the current branch and the branch to which\n> -\tyou are switching, the command refuses to switch branches in\n> -\torder to preserve your modifications in context.  However,\n> -\twith this option, a three-way merge between the current\n> -\tbranch, your working tree contents, and the new branch is\n> -\tdone, and you will be on the new branch.\n> -+\n> -When a merge conflict happens, the index entries for conflicting\n> -paths are left unmerged, and you need to resolve the conflicts\n> -and mark the resolved paths with `git add` (or `git rm` if the merge\n> -should result in deletion of the path).\n> +\tIf you have local modifications to one or more files that\n> +\tare different between the current branch and the branch to\n> +\twhich you are switching, the command normally refuses to\n> +\tswitch branches in order to preserve your modifications in\n> +\tcontext.  However, with this option, the conflicting local\n> +\tchanges are automatically stashed before the switch and\n> +\treapplied afterwards.  If the local changes do not overlap\n> +\twith the differences between branches, the switch proceeds\n> +\twithout stashing.  If reapplying the stash results in\n> +\tconflicts, the entry is saved to the stash list.  Resolve\n> +\tthe conflicts and run `git stash drop` when done, or clear\n> +\tthe working tree (e.g. with `git reset --hard`) before\n> +\trunning `git stash pop` later to re-apply your changes.\n>   \n>   `--conflict=<style>`::\n>   \tThe same as `--merge` option above, but changes the way the\n> @@ -217,15 +218,15 @@ $ git switch mytopic\n>   error: You have local changes to 'frotz'; not switching branches.\n>   ------------\n>   \n> -You can give the `-m` flag to the command, which would try a three-way\n> -merge:\n> +You can give the `-m` flag to the command, which would carry your local\n> +changes to the new branch:\n\nSame comment as for git-checkout.adoc\n\n>   ------------\n>   $ git switch -m mytopic\n> -Auto-merging frotz\n> +Switched to branch 'mytopic'\n\nDon't we show the modified files as well now?\n>   ------------\n>   \n> -After this three-way merge, the local modifications are _not_\n> +After the switch, the local modifications are reapplied and are _not_\n>   registered in your index file, so `git diff` would show you what\n>   changes you made since the tip of the new branch.\n>   \n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> @@ -755,7 +757,7 @@ static void setup_branch_path(struct branch_info *branch)\n>   \n>   static void init_topts(struct unpack_trees_options *topts, int merge,\n>   \t\t       int show_progress, int overwrite_ignore,\n> -\t\t       struct commit *old_commit)\n> +\t\t       struct commit *old_commit, bool show_unpack_errors)\n\nAs this function only sets up the flags for unpack_trees() I think we \ncould call this \"quiet\" or \"show_errors\"\n\n>   {\n>   \tmemset(topts, 0, sizeof(*topts));\n>   \ttopts->head_idx = -1;\n> @@ -767,7 +769,7 @@ static void init_topts(struct unpack_trees_options *topts, int merge,\n>   \ttopts->initial_checkout = is_index_unborn(the_repository->index);\n>   \ttopts->update = 1;\n>   \ttopts->merge = 1;\n> -\ttopts->quiet = merge && old_commit;\n> +\ttopts->quiet = merge && old_commit && !show_unpack_errors;\n\nWe've added a function parameter for this option but then we ignore it \nunless \"merge\" and \"old_commit\" are true which is confusing. The reason \nwe used to check those was to set \"quiet\" automatically but we can't do \nthat now, so why not just use the value the call requested?\n\n>   \ttopts->verbose_update = show_progress;\n>   \ttopts->fn = twoway_merge;\n>   \ttopts->preserve_ignored = !overwrite_ignore;\n> @@ -776,7 +778,8 @@ static void init_topts(struct unpack_trees_options *topts, int merge,\n>   static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\t\t      struct branch_info *old_branch_info,\n>   \t\t\t      struct branch_info *new_branch_info,\n> -\t\t\t      int *writeout_error)\n> +\t\t\t      int *writeout_error,\n\nThis is an \"out\" parameter, so it would make sense to keep it at the end \nof the parameter list.\n\n> +\t\t\t      bool show_unpack_errors)\n>   {\n> @@ -853,90 +857,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n>   \t\tret = unpack_trees(2, trees, &topts);\n>   \t\tclear_unpack_trees_porcelain(&topts);\n>   \t\tif (ret == -1) {\n> [...]\n> +\t\t\trollback_lock_file(&lock_file);\n> +\t\t\treturn MERGE_WORKING_TREE_UNPACK_FAILED;\n\nWe now use a unique return value when unpack_trees() fails - good.\n\n>   \t\t}\n>   \t}\n>   \n> @@ -1181,6 +1103,10 @@ static int switch_branches(const struct checkout_opts *opts,\n>   \tstruct object_id rev;\n>   \tint flag, writeout_error = 0;\n>   \tint do_merge = 1;\n> +\tint created_autostash = 0;\n> +\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n> +\tstruct strbuf autostash_msg = STRBUF_INIT;\n> +\tconst char *stash_label_base = NULL;\n>   \n>   \ttrace2_cmd_mode(\"branch\");\n>   \n> @@ -1218,11 +1144,49 @@ static int switch_branches(const struct checkout_opts *opts,\n> [...]\n>   \tif (do_merge) {\n> -\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n> +\t\t\t\t\t &writeout_error, false);\n> +\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n> +\t\t\tstrbuf_addf(&autostash_msg,\n> +\t\t\t\t    \"autostash while switching to '%s'\",\n\nIn an ideal world we'd create a message that said\n\n\tOn <new branch>: autostash while switching from '<old branch>'\n\nHowever I as discussed on patch 3, that would require us to be able to \noverride the branch name when creating a stash as that's where the \"On \n<branch>: \" prefix gets added.\n\n> +\t\t\t\t    new_branch_info->name);\n> +\t\t\tcreate_autostash_ref(the_repository,\n> +\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t     autostash_msg.buf, true);\n> +\t\t\tcreated_autostash = 1;\n> +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n> +\t\t\t\t\t\t &writeout_error, true);\n> +\t\t}\n> +\t\tif (created_autostash) {\n> +\t\t\tif (opts->conflict_style >= 0) {\n> +\t\t\t\tstruct strbuf cfg = STRBUF_INIT;\n> +\t\t\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n> +\t\t\t\t\t    conflict_style_name(opts->conflict_style));\n> +\t\t\t\tgit_config_push_parameter(cfg.buf);\n> +\t\t\t\tstrbuf_release(&cfg);\n> +\t\t\t}\n> +\t\t\tapply_autostash_ref(the_repository,\n> +\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t    new_branch_info->name,\n> +\t\t\t\t\t    \"local\",\n> +\t\t\t\t\t    stash_label_base,\n> +\t\t\t\t\t    autostash_msg.buf);\n> +\t\t}\n\nWe now have a single place where we restore the stashed changes - good\n\n> diff --git a/sequencer.c b/sequencer.c\n> index 7c0376d9e4..746f85a442 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4765,15 +4765,19 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n>   \t\tstrvec_push(&store.args, stash_oid);\n>   \t\tif (run_command(&store))\n>   \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n> +\t\telse if (attempt_apply)\n> +\t\t\tfprintf(stderr,\n> +\t\t\t\t_(\"Your local changes are stashed, however applying them\\n\"\n> +\t\t\t\t  \"resulted in conflicts.  You can either resolve the conflicts\\n\"\n> +\t\t\t\t  \"and then discard the stash with \\\"git stash drop\\\", or, if you\\n\"\n> +\t\t\t\t  \"do not want to resolve them now, run \\\"git reset --hard\\\" and\\n\"\n> +\t\t\t\t  \"apply the local changes later by running \\\"git stash pop\\\".\\n\"));\n>   \t\telse\n>   \t\t\tfprintf(stderr,\n> -\t\t\t\t_(\"%s\\n\"\n> +\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n>   \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n>   \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n> -\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n> -\t\t\t\tattempt_apply ?\n> -\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n> -\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n> +\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n>   \t}\n\nThis looks good now\n\n> diff --git a/t/t7201-co.sh b/t/t7201-co.sh\n> index 9bcf7c0b40..b3293ead8d 100755\n> --- a/t/t7201-co.sh\n> +++ b/t/t7201-co.sh\n> @@ -102,7 +102,7 @@ test_expect_success 'checkout -m with dirty tree' '\n>   \n>   \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n>   \n> -\tprintf \"M\\t%s\\n\" one >expect.messages &&\n> +\tprintf \"The following paths have local changes:\\nM\\t%s\\n\" one >expect.messages &&\n\nTo create a multi-line file it is clearer to use\n\n\tcat >expect.messages <<-\\EOF &&\n\tThe following paths have local changes:\n\tM\tone\n\tEOF\n\n>   \ttest_cmp expect.messages messages &&\n>   \n>   \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n> @@ -210,6 +210,65 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n>   \ttest_cmp expect two\n>   '\n>   \n> +test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 0 x y z >same &&\n> +\tgit add same &&\n> +\tfill 1 2 3 4 5 6 7 >one &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep \"Applied autostash\" actual &&\n> +\tfill 0 x y z >expect &&\n> +\ttest_cmp expect same &&\n> +\tfill 1 2 3 4 5 6 7 >expect &&\n> +\ttest_cmp expect one\n> +'\n> +\n> +test_expect_success 'checkout -m creates a recoverable stash on conflict' '\n> +\tgit checkout -f main &&\n> +\tgit clean -f &&\n> +\n> +\tfill 1 2 3 4 5 >one &&\n> +\ttest_must_fail git checkout side 2>stderr &&\n> +\ttest_grep \"Your local changes\" stderr &&\n> +\tgit checkout -m side >actual 2>&1 &&\n> +\ttest_grep \"resulted in conflicts\" actual &&\n> +\ttest_grep \"git stash drop\" actual &&\n> +\ttest_grep \"git stash pop\" actual &&\n> +\ttest_grep \"The following paths have local changes\" actual &&\n> +\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n\nI've realized since I suggested this that we should be checking the \nreflog message as well since that's what's shown by \"git stash list\" so\nwe need to run\n\n   git log -p -1 --format=\"%gs%n%B\" -g --diff-merges=1 refs/stash >actual\n\n> +\tsed /^index/d actual >actual.trimmed &&\n> +\tcat >expect <<-EOF &&\n\nand add\n\n   autostash while switching to ${SQ}side${SQ}\n\n> +\tOn main: autostash while switching to ${SQ}side${SQ}\n> +\n> +\tdiff --git a/one b/one\n> +\t--- a/one\n> +\t+++ b/one\n> +\t@@ -3,6 +3,3 @@\n> +\t 3\n> +\t 4\n> +\t 5\n> +\t-6\n> +\t-7\n> +\t-8\n> +\tEOF\n> +\ttest_cmp expect actual.trimmed &&\n> +\tgit stash drop &&\n> +\tgit reset --hard\n> +'\n> +\n> +test_expect_success 'checkout -m which would overwrite untracked file' '\n> +\tgit checkout -f --detach main &&\n> +\ttest_commit another-file &&\n> +\tgit checkout HEAD^ &&\n> +\t>another-file.t &&\n> +\tfill 1 2 3 4 5 >one &&\n> +\ttest_must_fail git checkout -m @{-1} 2>err &&\n> +\ttest_grep \"would be overwritten by checkout\" err &&\n> +\ttest_grep \"another-file.t\" err\n\nWhy the two calls to test_grep, rather than one? Anyway I've realized \nsince I suggested this test that we also need to check the message only \nappears once to prevent a regression where merge_working_tree() calls \nunpack_trees() without setting \"quiet\" the first time it is called. We \ncan do that by writing an expect file and calling test_cmp(), or by \nusing \"test_line_count = 1 err\"\n\nHopefully the next re-roll with be the final one\n\nThanks\n\nPhillip\n\n> +'\n> +\n>   test_expect_success 'switch to another branch while carrying a deletion' '\n>   \tgit checkout -f main &&\n>   \tgit reset --hard &&\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index 9838094b66..f877d9a433 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -914,7 +914,8 @@ test_expect_success 'merge with conflicted --autostash changes' '\n>   \tgit diff >expect &&\n>   \ttest_when_finished \"test_might_fail git stash drop\" &&\n>   \tgit merge --autostash c3 2>err &&\n> -\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n> +\ttest_grep \"applying them\" err &&\n> +\ttest_grep \"resulted in conflicts\" err &&\n>   \tgit show HEAD:file >merge-result &&\n>   \ttest_cmp result.1-9 merge-result &&\n>   \tgit stash show -p >actual &&\n> diff --git a/xdiff-interface.c b/xdiff-interface.c\n> index f043330f2a..5ee2b96d0a 100644\n> --- a/xdiff-interface.c\n> +++ b/xdiff-interface.c\n> @@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n>   \t\treturn -1;\n>   }\n>   \n> +const char *conflict_style_name(int style)\n> +{\n> +\tswitch (style) {\n> +\tcase XDL_MERGE_DIFF3:\n> +\t\treturn \"diff3\";\n> +\tcase XDL_MERGE_ZEALOUS_DIFF3:\n> +\t\treturn \"zdiff3\";\n> +\tdefault:\n> +\t\treturn \"merge\";\n> +\t}\n> +}\n> +\n>   int git_xmerge_style = -1;\n>   \n>   int git_xmerge_config(const char *var, const char *value,\n> diff --git a/xdiff-interface.h b/xdiff-interface.h\n> index fbc4ceec40..ce54e1c0e0 100644\n> --- a/xdiff-interface.h\n> +++ b/xdiff-interface.h\n> @@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n>   void xdiff_clear_find_func(xdemitconf_t *xecfg);\n>   struct config_context;\n>   int parse_conflict_style_name(const char *value);\n> +const char *conflict_style_name(int style);\n>   int git_xmerge_config(const char *var, const char *value,\n>   \t\t      const struct config_context *ctx, void *cb);\n>   extern int git_xmerge_style;\n\n"},{"id":"542423","messageId":"df5bf999-c545-4cb4-94da-b9062b026421@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"Re: [PATCH v15 0/5] checkout: 'autostash' for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-28T09:35:34Z","receivedAt":"2026-04-28T09:35:38Z","isPatch":true,"body":"Hi Harald\n\nOn 24/04/2026 22:10, Harald Nordgren via GitGitGadget wrote:\n>   * When the initial merge fails, the caller now knows for sure it was a\n>     conflict (not some unrelated error) before kicking off the autostash.\n> \n>   * If the second attempt also fails, the user now actually sees why.\n>     Previously errors like \"would be overwritten by checkout\" were being\n>     swallowed.\n> \n>   * The stash is reapplied right after the second attempt, so it won't get\n>     left behind if something else goes wrong later in the checkout.\n> \n>   * A couple of clean-up steps only run when we actually stashed, so regular\n>     checkouts aren't paying for work they don't need.\n> \n>   * Added a small header \"The following paths have local changes:\" before the\n>     list of changed files, so it doesn't look like part of the conflict\n>     advice above it.\n> \n>   * Reworded the conflict advice into a single paragraph. Feels less shouty\n>     and doesn't say \"local changes\" three times.\n> \n>   * Trimmed and consolidated tests in t7201. A bunch of the old ones couldn't\n>     actually fail, or were really testing other things; the conflict-path\n>     tests are now a single test that checks the stash entry directly.\n\nThanks for the summary, that all looks good. I've left some comments on \nPatches 1 and 3 but they can be future work and don't need to hold this \nseries up. There are a few small issues that need addressing on Patch 5 \nbut hopefully the next iteration will be the last.\n\nThanks\n\nPhillip\n\n> Harald Nordgren (5):\n>    stash: add --label-ours, --label-theirs, --label-base for apply\n>    sequencer: allow create_autostash to run silently\n>    sequencer: teach autostash apply to take optional conflict marker\n>      labels\n>    checkout: rollback lock on early returns in merge_working_tree\n>    checkout -m: autostash when switching branches\n> \n>   Documentation/git-checkout.adoc |  58 +++++------\n>   Documentation/git-stash.adoc    |  11 ++-\n>   Documentation/git-switch.adoc   |  33 +++----\n>   builtin/checkout.c              | 165 +++++++++++++++-----------------\n>   builtin/commit.c                |   3 +-\n>   builtin/merge.c                 |  15 ++-\n>   builtin/stash.c                 |  28 ++++--\n>   sequencer.c                     |  69 +++++++++----\n>   sequencer.h                     |   7 +-\n>   t/t3420-rebase-autostash.sh     |  16 ++--\n>   t/t3903-stash.sh                |  24 +++++\n>   t/t7201-co.sh                   |  61 +++++++++++-\n>   t/t7600-merge.sh                |   3 +-\n>   xdiff-interface.c               |  12 +++\n>   xdiff-interface.h               |   1 +\n>   xdiff/xmerge.c                  |   6 +-\n>   16 files changed, 335 insertions(+), 177 deletions(-)\n> \n> \n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v15\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v15\n> Pull-Request: https://github.com/git/git/pull/2234\n> \n> Range-diff vs v14:\n> \n>   1:  e18c25599a = 1:  aba8e6a9dc stash: add --label-ours, --label-theirs, --label-base for apply\n>   2:  ce29b10264 = 2:  89e0bfa803 sequencer: allow create_autostash to run silently\n>   3:  73051d1762 = 3:  a428ce7328 sequencer: teach autostash apply to take optional conflict marker labels\n>   4:  191058d8e3 = 4:  f358424085 checkout: rollback lock on early returns in merge_working_tree\n>   5:  86f33df1eb ! 5:  96b14db827 checkout -m: autostash when switching branches\n>       @@ builtin/checkout.c\n>         #include \"setup.h\"\n>         #include \"submodule.h\"\n>         #include \"symlinks.h\"\n>       +@@ builtin/checkout.c: struct checkout_opts {\n>       + \t.auto_advance = 1, \\\n>       + }\n>       +\n>       ++#define MERGE_WORKING_TREE_UNPACK_FAILED (-2)\n>       ++\n>       + struct branch_info {\n>       + \tchar *name; /* The short name used */\n>       + \tchar *path; /* The full name of a real branch */\n>       +@@ builtin/checkout.c: static void setup_branch_path(struct branch_info *branch)\n>       +\n>       + static void init_topts(struct unpack_trees_options *topts, int merge,\n>       + \t\t       int show_progress, int overwrite_ignore,\n>       +-\t\t       struct commit *old_commit)\n>       ++\t\t       struct commit *old_commit, bool show_unpack_errors)\n>       + {\n>       + \tmemset(topts, 0, sizeof(*topts));\n>       + \ttopts->head_idx = -1;\n>       +@@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, int merge,\n>       + \ttopts->initial_checkout = is_index_unborn(the_repository->index);\n>       + \ttopts->update = 1;\n>       + \ttopts->merge = 1;\n>       +-\ttopts->quiet = merge && old_commit;\n>       ++\ttopts->quiet = merge && old_commit && !show_unpack_errors;\n>       + \ttopts->verbose_update = show_progress;\n>       + \ttopts->fn = twoway_merge;\n>       + \ttopts->preserve_ignored = !overwrite_ignore;\n>       +@@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, int merge,\n>       + static int merge_working_tree(const struct checkout_opts *opts,\n>       + \t\t\t      struct branch_info *old_branch_info,\n>       + \t\t\t      struct branch_info *new_branch_info,\n>       +-\t\t\t      int *writeout_error)\n>       ++\t\t\t      int *writeout_error,\n>       ++\t\t\t      bool show_unpack_errors)\n>       + {\n>       + \tint ret;\n>       + \tstruct lock_file lock_file = LOCK_INIT;\n>       +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n>       +\n>       + \t\t/* 2-way merge to the new branch */\n>       + \t\tinit_topts(&topts, opts->merge, opts->show_progress,\n>       +-\t\t\t   opts->overwrite_ignore, old_branch_info->commit);\n>       ++\t\t\t   opts->overwrite_ignore, old_branch_info->commit,\n>       ++\t\t\t   show_unpack_errors);\n>       + \t\tinit_checkout_metadata(&topts.meta, new_branch_info->refname,\n>       + \t\t\t\t       new_branch_info->commit ?\n>       + \t\t\t\t       &new_branch_info->commit->object.oid :\n>        @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n>         \t\tret = unpack_trees(2, trees, &topts);\n>         \t\tclear_unpack_trees_porcelain(&topts);\n>       @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n>        -\t\t\t\treturn ret;\n>        -\t\t\t}\n>        +\t\t\trollback_lock_file(&lock_file);\n>       -+\t\t\treturn ret;\n>       ++\t\t\treturn MERGE_WORKING_TREE_UNPACK_FAILED;\n>         \t\t}\n>         \t}\n>         \n>       @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>         \t\t\tdo_merge = 0;\n>         \t}\n>         \n>       -+\tif (old_branch_info.name)\n>       ++\tif (old_branch_info.name) {\n>        +\t\tstash_label_base = old_branch_info.name;\n>       -+\telse if (old_branch_info.commit) {\n>       ++\t} else if (old_branch_info.commit) {\n>        +\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n>        +\t\t\t\t\t &old_branch_info.commit->object.oid,\n>        +\t\t\t\t\t DEFAULT_ABBREV);\n>       @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>        +\t}\n>        +\n>         \tif (do_merge) {\n>       - \t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>       -+\t\tif (ret == -1 && opts->merge) {\n>       +-\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>       ++\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n>       ++\t\t\t\t\t &writeout_error, false);\n>       ++\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n>        +\t\t\tstrbuf_addf(&autostash_msg,\n>        +\t\t\t\t    \"autostash while switching to '%s'\",\n>        +\t\t\t\t    new_branch_info->name);\n>       @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>        +\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n>        +\t\t\t\t\t     autostash_msg.buf, true);\n>        +\t\t\tcreated_autostash = 1;\n>       -+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>       ++\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n>       ++\t\t\t\t\t\t &writeout_error, true);\n>       ++\t\t}\n>       ++\t\tif (created_autostash) {\n>       ++\t\t\tif (opts->conflict_style >= 0) {\n>       ++\t\t\t\tstruct strbuf cfg = STRBUF_INIT;\n>       ++\t\t\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n>       ++\t\t\t\t\t    conflict_style_name(opts->conflict_style));\n>       ++\t\t\t\tgit_config_push_parameter(cfg.buf);\n>       ++\t\t\t\tstrbuf_release(&cfg);\n>       ++\t\t\t}\n>       ++\t\t\tapply_autostash_ref(the_repository,\n>       ++\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n>       ++\t\t\t\t\t    new_branch_info->name,\n>       ++\t\t\t\t\t    \"local\",\n>       ++\t\t\t\t\t    stash_label_base,\n>       ++\t\t\t\t\t    autostash_msg.buf);\n>        +\t\t}\n>         \t\tif (ret) {\n>       -+\t\t\tif (created_autostash)\n>       -+\t\t\t\tapply_autostash_ref(the_repository,\n>       -+\t\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n>       -+\t\t\t\t\t\t    new_branch_info->name,\n>       -+\t\t\t\t\t\t    \"local\",\n>       -+\t\t\t\t\t\t    stash_label_base,\n>       -+\t\t\t\t\t\t    autostash_msg.buf);\n>         \t\t\tbranch_info_release(&old_branch_info);\n>        -\t\t\treturn ret;\n>        +\t\t\tstrbuf_release(&old_commit_shortname);\n>       @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>         \n>         \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n>         \n>       -+\tif (opts->conflict_style >= 0) {\n>       -+\t\tstruct strbuf cfg = STRBUF_INIT;\n>       -+\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n>       -+\t\t\t    conflict_style_name(opts->conflict_style));\n>       -+\t\tgit_config_push_parameter(cfg.buf);\n>       -+\t\tstrbuf_release(&cfg);\n>       ++\tif (created_autostash) {\n>       ++\t\tdiscard_index(the_repository->index);\n>       ++\t\tif (repo_read_index(the_repository) < 0)\n>       ++\t\t\tdie(_(\"index file corrupt\"));\n>       ++\n>       ++\t\tif (!opts->quiet && new_branch_info->commit) {\n>       ++\t\t\tprintf(_(\"The following paths have local changes:\\n\"));\n>       ++\t\t\tshow_local_changes(&new_branch_info->commit->object,\n>       ++\t\t\t\t\t   &opts->diff_options);\n>       ++\t\t}\n>        +\t}\n>       -+\tapply_autostash_ref(the_repository, \"CHECKOUT_AUTOSTASH_HEAD\",\n>       -+\t\t\t    new_branch_info->name, \"local\",\n>       -+\t\t\t    stash_label_base,\n>       -+\t\t\t    autostash_msg.buf);\n>       -+\n>       -+\tdiscard_index(the_repository->index);\n>       -+\tif (repo_read_index(the_repository) < 0)\n>       -+\t\tdie(_(\"index file corrupt\"));\n>       -+\n>       -+\tif (created_autostash && !opts->quiet && new_branch_info->commit)\n>       -+\t\tshow_local_changes(&new_branch_info->commit->object,\n>       -+\t\t\t\t   &opts->diff_options);\n>        +\n>         \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n>         \tbranch_info_release(&old_branch_info);\n>       @@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int atte\n>         \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n>        +\t\telse if (attempt_apply)\n>        +\t\t\tfprintf(stderr,\n>       -+\t\t\t\t_(\"Your local changes are stashed, however, applying it to carry\\n\"\n>       -+\t\t\t\t  \"forward your local changes resulted in conflicts:\\n\"\n>       -+\t\t\t\t  \"\\n\"\n>       -+\t\t\t\t  \" - You can try resolving them now.  If you resolved them\\n\"\n>       -+\t\t\t\t  \"   successfully, discard the stash entry with \\\"git stash drop\\\".\\n\"\n>       -+\t\t\t\t  \"\\n\"\n>       -+\t\t\t\t  \" - Alternatively you can \\\"git reset --hard\\\" if you do not want\\n\"\n>       -+\t\t\t\t  \"   to deal with them right now, and later \\\"git stash pop\\\" to\\n\"\n>       -+\t\t\t\t  \"   recover your local changes.\\n\"));\n>       ++\t\t\t\t_(\"Your local changes are stashed, however applying them\\n\"\n>       ++\t\t\t\t  \"resulted in conflicts.  You can either resolve the conflicts\\n\"\n>       ++\t\t\t\t  \"and then discard the stash with \\\"git stash drop\\\", or, if you\\n\"\n>       ++\t\t\t\t  \"do not want to resolve them now, run \\\"git reset --hard\\\" and\\n\"\n>       ++\t\t\t\t  \"apply the local changes later by running \\\"git stash pop\\\".\\n\"));\n>         \t\telse\n>         \t\t\tfprintf(stderr,\n>        -\t\t\t\t_(\"%s\\n\"\n>       @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n>        -\tApplying autostash resulted in conflicts.\n>        -\tYour changes are safe in the stash.\n>        -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n>       -+\tYour local changes are stashed, however, applying it to carry\n>       -+\tforward your local changes resulted in conflicts:\n>       -+\n>       -+\t - You can try resolving them now.  If you resolved them\n>       -+\t   successfully, discard the stash entry with \"git stash drop\".\n>       -+\n>       -+\t - Alternatively you can \"git reset --hard\" if you do not want\n>       -+\t   to deal with them right now, and later \"git stash pop\" to\n>       -+\t   recover your local changes.\n>       ++\tYour local changes are stashed, however applying them\n>       ++\tresulted in conflicts.  You can either resolve the conflicts\n>       ++\tand then discard the stash with \"git stash drop\", or, if you\n>       ++\tdo not want to resolve them now, run \"git reset --hard\" and\n>       ++\tapply the local changes later by running \"git stash pop\".\n>         \tEOF\n>         }\n>         \n>       @@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () {\n>        -\tApplying autostash resulted in conflicts.\n>        -\tYour changes are safe in the stash.\n>        -\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n>       -+\tYour local changes are stashed, however, applying it to carry\n>       -+\tforward your local changes resulted in conflicts:\n>       -+\n>       -+\t - You can try resolving them now.  If you resolved them\n>       -+\t   successfully, discard the stash entry with \"git stash drop\".\n>       -+\n>       -+\t - Alternatively you can \"git reset --hard\" if you do not want\n>       -+\t   to deal with them right now, and later \"git stash pop\" to\n>       -+\t   recover your local changes.\n>       ++\tYour local changes are stashed, however applying them\n>       ++\tresulted in conflicts.  You can either resolve the conflicts\n>       ++\tand then discard the stash with \"git stash drop\", or, if you\n>       ++\tdo not want to resolve them now, run \"git reset --hard\" and\n>       ++\tapply the local changes later by running \"git stash pop\".\n>         \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n>         \tEOF\n>         }\n>        \n>         ## t/t7201-co.sh ##\n>       +@@ t/t7201-co.sh: test_expect_success 'checkout -m with dirty tree' '\n>       +\n>       + \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n>       +\n>       +-\tprintf \"M\\t%s\\n\" one >expect.messages &&\n>       ++\tprintf \"The following paths have local changes:\\nM\\t%s\\n\" one >expect.messages &&\n>       + \ttest_cmp expect.messages messages &&\n>       +\n>       + \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n>        @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n>         \ttest_cmp expect two\n>         '\n>         \n>       -+test_expect_success 'checkout --merge --conflict=zdiff3 <branch>' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit reset --hard &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill a b X d e >two &&\n>       -+\tgit checkout --merge --conflict=zdiff3 simple &&\n>       -+\n>       -+\tcat <<-EOF >expect &&\n>       -+\ta\n>       -+\t<<<<<<< simple\n>       -+\tc\n>       -+\t||||||| main\n>       -+\tb\n>       -+\tc\n>       -+\td\n>       -+\t=======\n>       -+\tb\n>       -+\tX\n>       -+\td\n>       -+\t>>>>>>> local\n>       -+\te\n>       -+\tEOF\n>       -+\ttest_cmp expect two\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m respects merge.conflictStyle config' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit reset --hard &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\ttest_config merge.conflictStyle diff3 &&\n>       -+\tfill b d >two &&\n>       -+\tgit checkout -m simple &&\n>       -+\n>       -+\tcat <<-EOF >expect &&\n>       -+\t<<<<<<< simple\n>       -+\ta\n>       -+\tc\n>       -+\te\n>       -+\t||||||| main\n>       -+\ta\n>       -+\tb\n>       -+\tc\n>       -+\td\n>       -+\te\n>       -+\t=======\n>       -+\tb\n>       -+\td\n>       -+\t>>>>>>> local\n>       -+\tEOF\n>       -+\ttest_cmp expect two\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m skips stash when no conflict' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill 0 x y z >same &&\n>       -+\tgit stash list >stash-before &&\n>       -+\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>       -+\tgit stash list >stash-after &&\n>       -+\ttest_cmp stash-before stash-after &&\n>       -+\tfill 0 x y z >expect &&\n>       -+\ttest_cmp expect same\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m skips stash with non-conflicting dirty index' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill 0 x y z >same &&\n>       -+\tgit add same &&\n>       -+\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>       -+\tfill 0 x y z >expect &&\n>       -+\ttest_cmp expect same\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m stashes and applies on conflicting changes' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill 1 2 3 4 5 6 7 >one &&\n>       -+\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>       -+\ttest_grep \"Applied autostash\" actual &&\n>       -+\tfill 1 2 3 4 5 6 7 >expect &&\n>       -+\ttest_cmp expect one\n>       -+'\n>       -+\n>        +test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n>        +\tgit checkout -f main &&\n>        +\tgit clean -f &&\n>       @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n>        +\tgit add same &&\n>        +\tfill 1 2 3 4 5 6 7 >one &&\n>        +\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>        +\ttest_grep \"Applied autostash\" actual &&\n>        +\tfill 0 x y z >expect &&\n>        +\ttest_cmp expect same &&\n>       @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n>        +\ttest_cmp expect one\n>        +'\n>        +\n>       -+test_expect_success 'checkout -m stashes on truly conflicting changes' '\n>       ++test_expect_success 'checkout -m creates a recoverable stash on conflict' '\n>        +\tgit checkout -f main &&\n>        +\tgit clean -f &&\n>        +\n>       @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n>        +\ttest_must_fail git checkout side 2>stderr &&\n>        +\ttest_grep \"Your local changes\" stderr &&\n>        +\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>        +\ttest_grep \"resulted in conflicts\" actual &&\n>        +\ttest_grep \"git stash drop\" actual &&\n>       ++\ttest_grep \"git stash pop\" actual &&\n>       ++\ttest_grep \"The following paths have local changes\" actual &&\n>       ++\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n>       ++\tsed /^index/d actual >actual.trimmed &&\n>       ++\tcat >expect <<-EOF &&\n>       ++\tOn main: autostash while switching to ${SQ}side${SQ}\n>       ++\n>       ++\tdiff --git a/one b/one\n>       ++\t--- a/one\n>       ++\t+++ b/one\n>       ++\t@@ -3,6 +3,3 @@\n>       ++\t 3\n>       ++\t 4\n>       ++\t 5\n>       ++\t-6\n>       ++\t-7\n>       ++\t-8\n>       ++\tEOF\n>       ++\ttest_cmp expect actual.trimmed &&\n>        +\tgit stash drop &&\n>        +\tgit reset --hard\n>        +'\n>        +\n>       -+test_expect_success 'checkout -m produces usable stash on conflict' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       ++test_expect_success 'checkout -m which would overwrite untracked file' '\n>       ++\tgit checkout -f --detach main &&\n>       ++\ttest_commit another-file &&\n>       ++\tgit checkout HEAD^ &&\n>       ++\t>another-file.t &&\n>        +\tfill 1 2 3 4 5 >one &&\n>       -+\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep \"recover your local changes\" actual &&\n>       -+\tgit checkout -f main &&\n>       -+\tgit stash pop &&\n>       -+\tfill 1 2 3 4 5 >expect &&\n>       -+\ttest_cmp expect one\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m autostash message includes target branch' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill 1 2 3 4 5 >one &&\n>       -+\tgit checkout -m side >actual 2>&1 &&\n>       -+\tgit stash list >stash-list &&\n>       -+\ttest_grep \"autostash while switching to .side.\" stash-list &&\n>       -+\tgit stash drop &&\n>       -+\tgit checkout -f main &&\n>       -+\tgit reset --hard\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m stashes on staged conflicting changes' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill 1 2 3 4 5 >one &&\n>       -+\tgit add one &&\n>       -+\tgit checkout -m side >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>       -+\ttest_grep \"resulted in conflicts\" actual &&\n>       -+\ttest_grep \"git stash drop\" actual &&\n>       -+\tgit stash drop &&\n>       -+\tgit reset --hard\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m applies stash cleanly with non-overlapping changes in same file' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit reset --hard &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tgit checkout -b nonoverlap_base &&\n>       -+\tfill a b c d >file &&\n>       -+\tgit add file &&\n>       -+\tgit commit -m \"add file\" &&\n>       -+\n>       -+\tgit checkout -b nonoverlap_child &&\n>       -+\tfill a b c INSERTED d >file &&\n>       -+\tgit commit -a -m \"insert line near end of file\" &&\n>       -+\n>       -+\tfill DIRTY a b c INSERTED d >file &&\n>       -+\n>       -+\tgit stash list >stash-before &&\n>       -+\tgit checkout -m nonoverlap_base 2>stderr &&\n>       -+\ttest_grep \"Applied autostash\" stderr &&\n>       -+\ttest_grep ! \"resulted in conflicts\" stderr &&\n>       -+\n>       -+\tgit stash list >stash-after &&\n>       -+\ttest_cmp stash-before stash-after &&\n>       -+\n>       -+\tfill DIRTY a b c d >expect &&\n>       -+\ttest_cmp expect file &&\n>       -+\n>       -+\tgit checkout -f main &&\n>       -+\tgit branch -D nonoverlap_base &&\n>       -+\tgit branch -D nonoverlap_child\n>       -+'\n>       -+\n>       -+test_expect_success 'checkout -m -b skips stash with dirty tree' '\n>       -+\tgit checkout -f main &&\n>       -+\tgit clean -f &&\n>       -+\n>       -+\tfill 0 x y z >same &&\n>       -+\tgit checkout -m -b newbranch >actual 2>&1 &&\n>       -+\ttest_grep ! \"Created autostash\" actual &&\n>       -+\tfill 0 x y z >expect &&\n>       -+\ttest_cmp expect same &&\n>       -+\tgit checkout main &&\n>       -+\tgit branch -D newbranch\n>       ++\ttest_must_fail git checkout -m @{-1} 2>err &&\n>       ++\ttest_grep \"would be overwritten by checkout\" err &&\n>       ++\ttest_grep \"another-file.t\" err\n>        +'\n>        +\n>         test_expect_success 'switch to another branch while carrying a deletion' '\n>       @@ t/t7600-merge.sh: test_expect_success 'merge with conflicted --autostash changes\n>         \ttest_when_finished \"test_might_fail git stash drop\" &&\n>         \tgit merge --autostash c3 2>err &&\n>        -\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n>       -+\ttest_grep \"your local changes resulted in conflicts\" err &&\n>       ++\ttest_grep \"applying them\" err &&\n>       ++\ttest_grep \"resulted in conflicts\" err &&\n>         \tgit show HEAD:file >merge-result &&\n>         \ttest_cmp result.1-9 merge-result &&\n>         \tgit stash show -p >actual &&\n> \n\n"},{"id":"542433","messageId":"20260428151648.55826-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"6d60573f-a02d-4aea-b891-6dd52e2d7048@gmail.com","subject":"[PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-28T15:16:48Z","receivedAt":"2026-04-28T15:16:52Z","isPatch":true,"body":"> This uses the existing label which is sensible, but I wonder if \"Stash\n> HEAD\" would be a better choice as the merge base is always HEAD commit\n> that the stash is based on.\n> \n> We can always change that later\n\nYeah, seems better to do later.\n\n\nHarald\n"},{"id":"542434","messageId":"20260428152107.56482-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"66e3b1f0-e00d-4b9b-8ee7-ca71444cf56d@gmail.com","subject":"[PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-28T15:21:07Z","receivedAt":"2026-04-28T15:21:10Z","isPatch":true,"body":"> It may well be that fixing all that turns out to be a lot of work as it\n> would mean modifying do_create_stash() to allow the branch name to be\n> overridden and modifying store_stash() to use the commit subject as the\n> reflog message in which case we should leave that for a future series.\n\nI suspect that it is a lot of work, so maybe also better to do later.\n\n\nHarald\n"},{"id":"542443","messageId":"20260428180856.92736-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"88a89e06-5223-4a6f-8f9e-66e72b632ee2@gmail.com","subject":"[PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-28T18:08:56Z","receivedAt":"2026-04-28T18:08:59Z","isPatch":true,"body":"> This is looking good, there are just a few small issues. Hopefully the\n> next iteration will be the last.\n\nThanks for the encouragement! 💪🏻\n\n> s/would/will/\n\n👍\n\n> It is the changes in the files overlapping that causes the merge\n> conflict, not the files overlapping\n> \n>         When the `--merge` (`-m`) option is given and the local changes\n>         overlap with the changes in the branch we're switching to,\n\n👍\n\n> I'd drop this line and say instead \"a message is printed\"\n\n👍\n\n> This needs updating to match the new conflict advice.\n\n👍\n\n> If you've not done so already it would be well worth checking the\n> generated git-checkout.html and the man page\n\nGood catch, I generated it now and yes it didn't look correct. I dropped\nthat last section now.\n\n> Don't we show the modified files as well now?\n\nGood catch, very good idea to actually generate the man html file and\ncheck.\n\n> As this function only sets up the flags for unpack_trees() I think we\n> could call this \"quiet\" or \"show_errors\"\n\nGood point!\n\n> We've added a function parameter for this option but then we ignore it\n> unless \"merge\" and \"old_commit\" are true which is confusing. The reason\n> we used to check those was to set \"quiet\" automatically but we can't do\n> that now, so why not just use the value the call requested?\n\nGood point! I attempted to change this, hopefully it doesn't break anything!\n\n> This is an \"out\" parameter, so it would make sense to keep it at the end\n> of the parameter list.\n\n👍\n\n> To create a multi-line file it is clearer to use\n> \n>         cat >expect.messages <<-\\EOF &&\n>         The following paths have local changes:\n>         M       one\n>         EOF\n\n👍\n\n> I've realized since I suggested this that we should be checking the\n> reflog message as well since that's what's shown by \"git stash list\" so\n> we need to run\n> \n>    git log -p -1 --format=\"%gs%n%B\" -g --diff-merges=1 refs/stash >actual\n> \n> > +     sed /^index/d actual >actual.trimmed &&\n> > +     cat >expect <<-EOF &&\n> \n> and add\n> \n> \n>    autostash while switching to ${SQ}side${SQ}\n\nMake sense!\n\n> Why the two calls to test_grep, rather than one? Anyway I've realized\n> since I suggested this test that we also need to check the message only\n> appears once to prevent a regression where merge_working_tree() calls\n> unpack_trees() without setting \"quiet\" the first time it is called. We\n> can do that by writing an expect file and calling test_cmp(), or by\n> using \"test_line_count = 1 err\"\n\nExcellent point. I went with test_cmp since it's multi-line output and\n\"test_line_count = 1\" seemed to not work then.\n\n\nHarald\n"},{"id":"542444","messageId":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v15.git.git.1777065012.gitgitgadget@gmail.com","subject":"[PATCH v16 0/5] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T18:39:07Z","receivedAt":"2026-04-28T18:39:16Z","isPatch":true,"body":" * Updated the git checkout and git switch docs to show the actual output\n   when using -m to carry local changes across a branch switch, and changed\n   \"would carry\" to \"will carry\".\n * Rewrote the merge-conflict example in both docs to match the new, more\n   concise message printed on autostash conflicts.\n * Replaced the show_unpack_errors flag and old_commit parameter in\n   merge_working_tree()/init_topts() with a plain quiet boolean, so the\n   caller decides directly whether to suppress unpack errors.\n * Tightened the 'checkout -m with dirty tree' test by replacing a printf\n   with a heredoc.\n * Made the 'checkout -m creates a recoverable stash on conflict' test also\n   assert the reflog subject of the new stash entry.\n * Replaced two test_grep calls in the 'checkout -m which would overwrite\n   untracked file' test with a single test_cmp, which also catches a\n   regression where the \"would be overwritten\" message could end up printed\n   twice.\n\nHarald Nordgren (5):\n  stash: add --label-ours, --label-theirs, --label-base for apply\n  sequencer: allow create_autostash to run silently\n  sequencer: teach autostash apply to take optional conflict marker\n    labels\n  checkout: rollback lock on early returns in merge_working_tree\n  checkout -m: autostash when switching branches\n\n Documentation/git-checkout.adoc |  55 +++++------\n Documentation/git-stash.adoc    |  11 ++-\n Documentation/git-switch.adoc   |  36 ++++---\n builtin/checkout.c              | 166 +++++++++++++++-----------------\n builtin/commit.c                |   3 +-\n builtin/merge.c                 |  15 ++-\n builtin/stash.c                 |  28 ++++--\n sequencer.c                     |  69 +++++++++----\n sequencer.h                     |   7 +-\n t/t3420-rebase-autostash.sh     |  16 +--\n t/t3903-stash.sh                |  24 +++++\n t/t7201-co.sh                   |  71 +++++++++++++-\n t/t7600-merge.sh                |   3 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n xdiff/xmerge.c                  |   6 +-\n 16 files changed, 343 insertions(+), 180 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v16\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v16\nPull-Request: https://github.com/git/git/pull/2234\n\nRange-diff vs v15:\n\n 1:  aba8e6a9dc = 1:  aba8e6a9dc stash: add --label-ours, --label-theirs, --label-base for apply\n 2:  89e0bfa803 = 2:  89e0bfa803 sequencer: allow create_autostash to run silently\n 3:  a428ce7328 = 3:  a428ce7328 sequencer: teach autostash apply to take optional conflict marker labels\n 4:  f358424085 = 4:  f358424085 checkout: rollback lock on early returns in merge_working_tree\n 5:  96b14db827 ! 5:  07d25fda91 checkout -m: autostash when switching branches\n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n       \n      -You can give the `-m` flag to the command, which would try a\n      -three-way merge:\n     -+You can give the `-m` flag to the command, which would carry your local\n     ++You can give the `-m` flag to the command, which will carry your local\n      +changes to the new branch:\n       \n       ------------\n       $ git checkout -m mytopic\n      -Auto-merging frotz\n     ++Applied autostash.\n      +Switched to branch 'mytopic'\n     ++The following paths have local changes:\n     ++M\tfrotz\n       ------------\n       \n      -After this three-way merge, the local modifications are _not_\n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n       \n      -When a merge conflict happens during switching branches with\n      -the `-m` option, you would see something like this:\n     -+When the `--merge` (`-m`) option is in effect and the locally\n     -+modified files overlap with files that need to be updated by the\n     -+branch switch, the changes are stashed and reapplied after the\n     -+switch.  If this process results in conflicts, a stash entry is saved\n     -+and made available in `git stash list`:\n     ++When the `--merge` (`-m`) option is given and the local changes\n     ++overlap with the changes in the branch we're switching to, the\n     ++changes are stashed and reapplied after the switch.  If this\n     ++process results in conflicts, the stash entry is saved and a\n     ++message is printed:\n       \n       ------------\n       $ git checkout -m mytopic\n     @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n      -ERROR: Merge conflict in frotz\n      -fatal: merge program failed\n      -------------\n     -+Your local changes are stashed, however, applying it to carry\n     -+forward your local changes resulted in conflicts:\n     - \n     +-\n      -At this point, `git diff` shows the changes cleanly merged as in\n      -the previous example, as well as the changes in the conflicted\n      -files.  Edit and resolve the conflict and mark it resolved with\n      -`git add` as usual:\n     -+ - You can try resolving them now.  If you resolved them\n     -+   successfully, discard the stash entry with \"git stash drop\".\n     - \n     -+ - Alternatively you can \"git reset --hard\" if you do not want\n     -+   to deal with them right now, and later \"git stash pop\" to\n     -+   recover your local changes.\n     - ------------\n     +-\n     +-------------\n      -$ edit frotz\n      -$ git add frotz\n     --------------\n     -+\n     -+You can try resolving the conflicts now.  Edit the conflicting files\n     -+and mark them resolved with `git add` as usual, then run `git stash\n     -+drop` to discard the stash entry.  Alternatively, you can clear the\n     -+working tree with `git reset --hard` and recover your local changes\n     -+later with `git stash pop`.\n     ++Your local changes are stashed, however applying them\n     ++resulted in conflicts.  You can either resolve the conflicts\n     ++and then discard the stash with \"git stash drop\", or, if you\n     ++do not want to resolve them now, run \"git reset --hard\" and\n     ++apply the local changes later by running \"git stash pop\".\n     + ------------\n       \n       CONFIGURATION\n     - -------------\n      \n       ## Documentation/git-switch.adoc ##\n      @@ Documentation/git-switch.adoc: variable.\n     @@ Documentation/git-switch.adoc: $ git switch mytopic\n       \n      -You can give the `-m` flag to the command, which would try a three-way\n      -merge:\n     -+You can give the `-m` flag to the command, which would carry your local\n     ++You can give the `-m` flag to the command, which will carry your local\n      +changes to the new branch:\n       \n       ------------\n       $ git switch -m mytopic\n      -Auto-merging frotz\n     ++Applied autostash.\n      +Switched to branch 'mytopic'\n     ++The following paths have local changes:\n     ++M\tfrotz\n       ------------\n       \n      -After this three-way merge, the local modifications are _not_\n     @@ builtin/checkout.c: struct checkout_opts {\n       \tchar *name; /* The short name used */\n       \tchar *path; /* The full name of a real branch */\n      @@ builtin/checkout.c: static void setup_branch_path(struct branch_info *branch)\n     + \tbranch->path = strbuf_detach(&buf, NULL);\n     + }\n       \n     - static void init_topts(struct unpack_trees_options *topts, int merge,\n     +-static void init_topts(struct unpack_trees_options *topts, int merge,\n     ++static void init_topts(struct unpack_trees_options *topts,\n       \t\t       int show_progress, int overwrite_ignore,\n      -\t\t       struct commit *old_commit)\n     -+\t\t       struct commit *old_commit, bool show_unpack_errors)\n     ++\t\t       bool quiet)\n       {\n       \tmemset(topts, 0, sizeof(*topts));\n       \ttopts->head_idx = -1;\n     @@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i\n       \ttopts->update = 1;\n       \ttopts->merge = 1;\n      -\ttopts->quiet = merge && old_commit;\n     -+\ttopts->quiet = merge && old_commit && !show_unpack_errors;\n     ++\ttopts->quiet = quiet;\n       \ttopts->verbose_update = show_progress;\n       \ttopts->fn = twoway_merge;\n       \ttopts->preserve_ignored = !overwrite_ignore;\n     @@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i\n       static int merge_working_tree(const struct checkout_opts *opts,\n       \t\t\t      struct branch_info *old_branch_info,\n       \t\t\t      struct branch_info *new_branch_info,\n     --\t\t\t      int *writeout_error)\n     -+\t\t\t      int *writeout_error,\n     -+\t\t\t      bool show_unpack_errors)\n     ++\t\t\t      bool quiet,\n     + \t\t\t      int *writeout_error)\n       {\n       \tint ret;\n     - \tstruct lock_file lock_file = LOCK_INIT;\n      @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n     + \t\t}\n       \n       \t\t/* 2-way merge to the new branch */\n     - \t\tinit_topts(&topts, opts->merge, opts->show_progress,\n     +-\t\tinit_topts(&topts, opts->merge, opts->show_progress,\n      -\t\t\t   opts->overwrite_ignore, old_branch_info->commit);\n     -+\t\t\t   opts->overwrite_ignore, old_branch_info->commit,\n     -+\t\t\t   show_unpack_errors);\n     ++\t\tinit_topts(&topts, opts->show_progress,\n     ++\t\t\t   opts->overwrite_ignore, quiet);\n       \t\tinit_checkout_metadata(&topts.meta, new_branch_info->refname,\n       \t\t\t\t       new_branch_info->commit ?\n       \t\t\t\t       &new_branch_info->commit->object.oid :\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \tif (do_merge) {\n      -\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n      +\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n     -+\t\t\t\t\t &writeout_error, false);\n     ++\t\t\t\t\t opts->merge, &writeout_error);\n      +\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n      +\t\t\tstrbuf_addf(&autostash_msg,\n      +\t\t\t\t    \"autostash while switching to '%s'\",\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n      +\t\t\t\t\t     autostash_msg.buf, true);\n      +\t\t\tcreated_autostash = 1;\n      +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n     -+\t\t\t\t\t\t &writeout_error, true);\n     ++\t\t\t\t\t\t false, &writeout_error);\n      +\t\t}\n      +\t\tif (created_autostash) {\n      +\t\t\tif (opts->conflict_style >= 0) {\n     @@ t/t7201-co.sh: test_expect_success 'checkout -m with dirty tree' '\n       \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n       \n      -\tprintf \"M\\t%s\\n\" one >expect.messages &&\n     -+\tprintf \"The following paths have local changes:\\nM\\t%s\\n\" one >expect.messages &&\n     ++\tcat >expect.messages <<-\\EOF &&\n     ++\tThe following paths have local changes:\n     ++\tM\tone\n     ++\tEOF\n       \ttest_cmp expect.messages messages &&\n       \n       \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\ttest_grep \"git stash drop\" actual &&\n      +\ttest_grep \"git stash pop\" actual &&\n      +\ttest_grep \"The following paths have local changes\" actual &&\n     -+\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n     ++\tgit log -p -1 --format=\"%gs%n%B\" -g --diff-merges=1 refs/stash >actual &&\n      +\tsed /^index/d actual >actual.trimmed &&\n      +\tcat >expect <<-EOF &&\n     ++\tautostash while switching to ${SQ}side${SQ}\n      +\tOn main: autostash while switching to ${SQ}side${SQ}\n      +\n      +\tdiff --git a/one b/one\n     @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n      +\t>another-file.t &&\n      +\tfill 1 2 3 4 5 >one &&\n      +\ttest_must_fail git checkout -m @{-1} 2>err &&\n     -+\ttest_grep \"would be overwritten by checkout\" err &&\n     -+\ttest_grep \"another-file.t\" err\n     ++\tq_to_tab >expect <<-\\EOF &&\n     ++\terror: The following untracked working tree files would be overwritten by checkout:\n     ++\tQanother-file.t\n     ++\tPlease move or remove them before you switch branches.\n     ++\tAborting\n     ++\tApplied autostash.\n     ++\tEOF\n     ++\ttest_cmp expect err\n      +'\n      +\n       test_expect_success 'switch to another branch while carrying a deletion' '\n\n-- \ngitgitgadget\n"},{"id":"542445","messageId":"aba8e6a9dc7e1620a46888685bb081f878cc6932.1777401552.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"[PATCH v16 1/5] stash: add --label-ours, --label-theirs, --label-base for apply","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T18:39:08Z","receivedAt":"2026-04-28T18:39:17Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAllow callers of \"git stash apply\" to pass custom labels for conflict\nmarkers instead of the default \"Updated upstream\" and \"Stashed changes\".\nDocument the new options and add a test.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-stash.adoc | 11 ++++++++++-\n builtin/stash.c              | 28 ++++++++++++++++++++--------\n t/t3903-stash.sh             | 24 ++++++++++++++++++++++++\n xdiff/xmerge.c               |  6 +++---\n 4 files changed, 57 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc\nindex b05c990ecd..50bb89f483 100644\n--- a/Documentation/git-stash.adoc\n+++ b/Documentation/git-stash.adoc\n@@ -12,7 +12,7 @@ git stash list [<log-options>]\n git stash show [-u | --include-untracked | --only-untracked] [<diff-options>] [<stash>]\n git stash drop [-q | --quiet] [<stash>]\n git stash pop [--index] [-q | --quiet] [<stash>]\n-git stash apply [--index] [-q | --quiet] [<stash>]\n+git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\n git stash branch <branchname> [<stash>]\n git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n \t     [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n@@ -195,6 +195,15 @@ the index's ones. However, this can fail, when you have conflicts\n (which are stored in the index, where you therefore can no longer\n apply the changes as they were originally).\n \n+`--label-ours=<label>`::\n+`--label-theirs=<label>`::\n+`--label-base=<label>`::\n+\tThese options are only valid for the `apply` command.\n++\n+Use the given labels in conflict markers instead of the default\n+\"Updated upstream\", \"Stashed changes\", and \"Stash base\".\n+`--label-base` only has an effect with merge.conflictStyle=diff3.\n+\n `-k`::\n `--keep-index`::\n `--no-keep-index`::\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 0d27b2fb1f..32dbc97b47 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -44,7 +44,7 @@\n #define BUILTIN_STASH_POP_USAGE \\\n \tN_(\"git stash pop [--index] [-q | --quiet] [<stash>]\")\n #define BUILTIN_STASH_APPLY_USAGE \\\n-\tN_(\"git stash apply [--index] [-q | --quiet] [<stash>]\")\n+\tN_(\"git stash apply [--index] [-q | --quiet] [--label-ours=<label>] [--label-theirs=<label>] [--label-base=<label>] [<stash>]\")\n #define BUILTIN_STASH_BRANCH_USAGE \\\n \tN_(\"git stash branch <branchname> [<stash>]\")\n #define BUILTIN_STASH_STORE_USAGE \\\n@@ -591,7 +591,9 @@ static void unstage_changes_unless_new(struct object_id *orig_tree)\n }\n \n static int do_apply_stash(const char *prefix, struct stash_info *info,\n-\t\t\t  int index, int quiet)\n+\t\t\t  int index, int quiet,\n+\t\t\t  const char *label_ours, const char *label_theirs,\n+\t\t\t  const char *label_base)\n {\n \tint clean, ret;\n \tint has_index = index;\n@@ -643,9 +645,9 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \n \tinit_ui_merge_options(&o, the_repository);\n \n-\to.branch1 = \"Updated upstream\";\n-\to.branch2 = \"Stashed changes\";\n-\to.ancestor = \"Stash base\";\n+\to.branch1 = label_ours ? label_ours : \"Updated upstream\";\n+\to.branch2 = label_theirs ? label_theirs : \"Stashed changes\";\n+\to.ancestor = label_base ? label_base : \"Stash base\";\n \n \tif (oideq(&info->b_tree, &c_tree))\n \t\to.branch1 = \"Version stash was based on\";\n@@ -723,11 +725,18 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tint ret = -1;\n \tint quiet = 0;\n \tint index = use_index;\n+\tconst char *label_ours = NULL, *label_theirs = NULL, *label_base = NULL;\n \tstruct stash_info info = STASH_INFO_INIT;\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet, only report errors\")),\n \t\tOPT_BOOL(0, \"index\", &index,\n \t\t\t N_(\"attempt to recreate the index\")),\n+\t\tOPT_STRING(0, \"label-ours\", &label_ours, N_(\"label\"),\n+\t\t\t   N_(\"label for the upstream side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-theirs\", &label_theirs, N_(\"label\"),\n+\t\t\t   N_(\"label for the stashed side in conflict markers\")),\n+\t\tOPT_STRING(0, \"label-base\", &label_base, N_(\"label\"),\n+\t\t\t   N_(\"label for the base in diff3 conflict markers\")),\n \t\tOPT_END()\n \t};\n \n@@ -737,7 +746,8 @@ static int apply_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tret = do_apply_stash(prefix, &info, index, quiet);\n+\tret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t     label_ours, label_theirs, label_base);\n cleanup:\n \tfree_stash_info(&info);\n \treturn ret;\n@@ -836,7 +846,8 @@ static int pop_stash(int argc, const char **argv, const char *prefix,\n \tif (get_stash_info_assert(&info, argc, argv))\n \t\tgoto cleanup;\n \n-\tif ((ret = do_apply_stash(prefix, &info, index, quiet)))\n+\tif ((ret = do_apply_stash(prefix, &info, index, quiet,\n+\t\t\t\t  NULL, NULL, NULL)))\n \t\tprintf_ln(_(\"The stash entry is kept in case \"\n \t\t\t    \"you need it again.\"));\n \telse\n@@ -877,7 +888,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix,\n \tstrvec_push(&cp.args, oid_to_hex(&info.b_commit));\n \tret = run_command(&cp);\n \tif (!ret)\n-\t\tret = do_apply_stash(prefix, &info, 1, 0);\n+\t\tret = do_apply_stash(prefix, &info, 1, 0,\n+\t\t\t\t     NULL, NULL, NULL);\n \tif (!ret && info.is_stash_ref)\n \t\tret = do_drop_stash(&info, 0);\n \ndiff --git a/t/t3903-stash.sh b/t/t3903-stash.sh\nindex 70879941c2..bdaad22e1f 100755\n--- a/t/t3903-stash.sh\n+++ b/t/t3903-stash.sh\n@@ -56,6 +56,7 @@ setup_stash() {\n \tgit add other-file &&\n \ttest_tick &&\n \tgit commit -m initial &&\n+\tgit tag initial &&\n \techo 2 >file &&\n \tgit add file &&\n \techo 3 >file &&\n@@ -1790,4 +1791,27 @@ test_expect_success 'stash.index=false overridden by --index' '\n \ttest_cmp expect file\n '\n \n+test_expect_success 'apply with custom conflict labels' '\n+\tgit reset --hard initial &&\n+\ttest_commit label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git -c merge.conflictStyle=diff3 stash apply --label-ours=UP --label-theirs=STASH &&\n+\ttest_grep \"^<<<<<<< UP\" conflict-file &&\n+\ttest_grep \"^||||||| Stash base\" conflict-file &&\n+\ttest_grep \"^>>>>>>> STASH\" conflict-file\n+'\n+\n+test_expect_success 'apply with empty conflict labels' '\n+\tgit reset --hard initial &&\n+\ttest_commit empty-label-base conflict-file base-content &&\n+\techo stashed >conflict-file &&\n+\tgit stash push -m \"stashed\" &&\n+\ttest_commit empty-label-upstream conflict-file upstream-content &&\n+\ttest_must_fail git stash apply --label-ours= --label-theirs= &&\n+\ttest_grep \"^<<<<<<<$\" conflict-file &&\n+\ttest_grep \"^>>>>>>>$\" conflict-file\n+'\n+\n test_done\ndiff --git a/xdiff/xmerge.c b/xdiff/xmerge.c\nindex 29dad98c49..659ad4ec97 100644\n--- a/xdiff/xmerge.c\n+++ b/xdiff/xmerge.c\n@@ -199,9 +199,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,\n \t\t\t      int size, int i, int style,\n \t\t\t      xdmerge_t *m, char *dest, int marker_size)\n {\n-\tint marker1_size = (name1 ? strlen(name1) + 1 : 0);\n-\tint marker2_size = (name2 ? strlen(name2) + 1 : 0);\n-\tint marker3_size = (name3 ? strlen(name3) + 1 : 0);\n+\tint marker1_size = (name1 && *name1 ? strlen(name1) + 1 : 0);\n+\tint marker2_size = (name2 && *name2 ? strlen(name2) + 1 : 0);\n+\tint marker3_size = (name3 && *name3 ? strlen(name3) + 1 : 0);\n \tint needs_cr = is_cr_needed(xe1, xe2, m);\n \n \tif (marker_size <= 0)\n-- \ngitgitgadget\n\n"},{"id":"542446","messageId":"89e0bfa80326d20d11355ed2e73de0907e5c9e41.1777401552.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"[PATCH v16 2/5] sequencer: allow create_autostash to run silently","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T18:39:09Z","receivedAt":"2026-04-28T18:39:19Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd a silent parameter to create_autostash_internal and introduce\ncreate_autostash_ref_silent so that callers can create an autostash\nwithout printing the \"Created autostash\" message.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/merge.c |  6 ++++--\n sequencer.c     | 17 +++++++++++------\n sequencer.h     |  3 ++-\n 3 files changed, 17 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 2cbce56f8d..3ebe190ef1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1672,7 +1672,8 @@ int cmd_merge(int argc,\n \t\t}\n \n \t\tif (autostash)\n-\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t     NULL, false);\n \t\tif (checkout_fast_forward(the_repository,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n@@ -1764,7 +1765,8 @@ int cmd_merge(int argc,\n \t\tdie_ff_impossible();\n \n \tif (autostash)\n-\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tcreate_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t     NULL, false);\n \n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..ff5258f481 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4657,7 +4657,9 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)\n \n static void create_autostash_internal(struct repository *r,\n \t\t\t\t      const char *path,\n-\t\t\t\t      const char *refname)\n+\t\t\t\t      const char *refname,\n+\t\t\t\t      const char *message,\n+\t\t\t\t      bool silent)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct lock_file lock_file = LOCK_INIT;\n@@ -4679,7 +4681,8 @@ static void create_autostash_internal(struct repository *r,\n \t\tstruct object_id oid;\n \n \t\tstrvec_pushl(&stash.args,\n-\t\t\t     \"stash\", \"create\", \"autostash\", NULL);\n+\t\t\t     \"stash\", \"create\",\n+\t\t\t     message ? message : \"autostash\", NULL);\n \t\tstash.git_cmd = 1;\n \t\tstash.no_stdin = 1;\n \t\tstrbuf_reset(&buf);\n@@ -4702,7 +4705,8 @@ static void create_autostash_internal(struct repository *r,\n \t\t\t\t\t&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);\n \t\t}\n \n-\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n+\t\tif (!silent)\n+\t\t\tprintf(_(\"Created autostash: %s\\n\"), buf.buf);\n \t\tif (reset_head(r, &ropts) < 0)\n \t\t\tdie(_(\"could not reset --hard\"));\n \t\tdiscard_index(r->index);\n@@ -4714,12 +4718,13 @@ static void create_autostash_internal(struct repository *r,\n \n void create_autostash(struct repository *r, const char *path)\n {\n-\tcreate_autostash_internal(r, path, NULL);\n+\tcreate_autostash_internal(r, path, NULL, NULL, false);\n }\n \n-void create_autostash_ref(struct repository *r, const char *refname)\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent)\n {\n-\tcreate_autostash_internal(r, NULL, refname);\n+\tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\ndiff --git a/sequencer.h b/sequencer.h\nindex a6fa670c7c..02d2d9db06 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -229,7 +229,8 @@ void commit_post_rewrite(struct repository *r,\n \t\t\t const struct object_id *new_head);\n \n void create_autostash(struct repository *r, const char *path);\n-void create_autostash_ref(struct repository *r, const char *refname);\n+void create_autostash_ref(struct repository *r, const char *refname,\n+\t\t\t  const char *message, bool silent);\n int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n-- \ngitgitgadget\n\n"},{"id":"542447","messageId":"a428ce73281dd288f0c4f44c4f92c03129556b7f.1777401552.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"[PATCH v16 3/5] sequencer: teach autostash apply to take optional conflict marker labels","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T18:39:10Z","receivedAt":"2026-04-28T18:39:21Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nAdd label_ours, label_theirs, label_base, and stash_msg parameters to\napply_autostash_ref() and the autostash apply machinery so callers can\npass custom conflict marker labels through to\n\"git stash apply --label-ours/--label-theirs/--label-base\", as well as\na custom stash message for \"git stash store -m\".\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/commit.c |  3 ++-\n builtin/merge.c  |  9 ++++++---\n sequencer.c      | 38 +++++++++++++++++++++++++++++---------\n sequencer.h      |  4 +++-\n 4 files changed, 40 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex a3e52ac9ca..28f6174503 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1979,7 +1979,8 @@ int cmd_commit(int argc,\n \t\t\t\t     &oid, flags);\n \t}\n \n-\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t    NULL, NULL, NULL, NULL);\n \n cleanup:\n \tfree_commit_extra_headers(extra);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 3ebe190ef1..aacf8c524e 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -537,7 +537,8 @@ static void finish(struct commit *head_commit,\n \trun_hooks_l(the_repository, \"post-merge\", squash ? \"1\" : \"0\", NULL);\n \n \tif (new_head)\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \tstrbuf_release(&reflog_message);\n }\n \n@@ -1678,7 +1679,8 @@ int cmd_merge(int argc,\n \t\t\t\t\t  &head_commit->object.oid,\n \t\t\t\t\t  &commit->object.oid,\n \t\t\t\t\t  overwrite_ignore)) {\n-\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\t\tret = 1;\n \t\t\tgoto done;\n \t\t}\n@@ -1851,7 +1853,8 @@ int cmd_merge(int argc,\n \t\telse\n \t\t\tfprintf(stderr, _(\"Merge with strategy %s failed.\\n\"),\n \t\t\t\tuse_strategies[0]->name);\n-\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n+\t\tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\",\n+\t\t\t\t    NULL, NULL, NULL, NULL);\n \t\tret = 2;\n \t\tgoto done;\n \t} else if (best_strategy == wt_strategy)\ndiff --git a/sequencer.c b/sequencer.c\nindex ff5258f481..7c0376d9e4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4727,7 +4727,10 @@ void create_autostash_ref(struct repository *r, const char *refname,\n \tcreate_autostash_internal(r, NULL, refname, message, silent);\n }\n \n-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n+static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct child_process child = CHILD_PROCESS_INIT;\n \tint ret = 0;\n@@ -4738,6 +4741,12 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tchild.no_stderr = 1;\n \t\tstrvec_push(&child.args, \"stash\");\n \t\tstrvec_push(&child.args, \"apply\");\n+\t\tif (label_ours)\n+\t\t\tstrvec_pushf(&child.args, \"--label-ours=%s\", label_ours);\n+\t\tif (label_theirs)\n+\t\t\tstrvec_pushf(&child.args, \"--label-theirs=%s\", label_theirs);\n+\t\tif (label_base)\n+\t\t\tstrvec_pushf(&child.args, \"--label-base=%s\", label_base);\n \t\tstrvec_push(&child.args, stash_oid);\n \t\tret = run_command(&child);\n \t}\n@@ -4751,7 +4760,7 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)\n \t\tstrvec_push(&store.args, \"stash\");\n \t\tstrvec_push(&store.args, \"store\");\n \t\tstrvec_push(&store.args, \"-m\");\n-\t\tstrvec_push(&store.args, \"autostash\");\n+\t\tstrvec_push(&store.args, stash_msg ? stash_msg : \"autostash\");\n \t\tstrvec_push(&store.args, \"-q\");\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n@@ -4782,7 +4791,8 @@ static int apply_save_autostash(const char *path, int attempt_apply)\n \t}\n \tstrbuf_trim(&stash_oid);\n \n-\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid.buf, attempt_apply,\n+\t\t\t\t      NULL, NULL, NULL, NULL);\n \n \tunlink(path);\n \tstrbuf_release(&stash_oid);\n@@ -4801,11 +4811,14 @@ int apply_autostash(const char *path)\n \n int apply_autostash_oid(const char *stash_oid)\n {\n-\treturn apply_save_autostash_oid(stash_oid, 1);\n+\treturn apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL, NULL);\n }\n \n static int apply_save_autostash_ref(struct repository *r, const char *refname,\n-\t\t\t\t    int attempt_apply)\n+\t\t\t\t    int attempt_apply,\n+\t\t\t\t    const char *label_ours, const char *label_theirs,\n+\t\t\t\t    const char *label_base,\n+\t\t\t\t    const char *stash_msg)\n {\n \tstruct object_id stash_oid;\n \tchar stash_oid_hex[GIT_MAX_HEXSZ + 1];\n@@ -4821,7 +4834,9 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \t\treturn error(_(\"autostash reference is a symref\"));\n \n \toid_to_hex_r(stash_oid_hex, &stash_oid);\n-\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);\n+\tret = apply_save_autostash_oid(stash_oid_hex, attempt_apply,\n+\t\t\t\t       label_ours, label_theirs, label_base,\n+\t\t\t\t       stash_msg);\n \n \trefs_delete_ref(get_main_ref_store(r), \"\", refname,\n \t\t\t&stash_oid, REF_NO_DEREF);\n@@ -4831,12 +4846,17 @@ static int apply_save_autostash_ref(struct repository *r, const char *refname,\n \n int save_autostash_ref(struct repository *r, const char *refname)\n {\n-\treturn apply_save_autostash_ref(r, refname, 0);\n+\treturn apply_save_autostash_ref(r, refname, 0,\n+\t\t\t\t\tNULL, NULL, NULL, NULL);\n }\n \n-int apply_autostash_ref(struct repository *r, const char *refname)\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg)\n {\n-\treturn apply_save_autostash_ref(r, refname, 1);\n+\treturn apply_save_autostash_ref(r, refname, 1,\n+\t\t\t\t\tlabel_ours, label_theirs, label_base,\n+\t\t\t\t\tstash_msg);\n }\n \n static int checkout_onto(struct repository *r, struct replay_opts *opts,\ndiff --git a/sequencer.h b/sequencer.h\nindex 02d2d9db06..3164bd437d 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -235,7 +235,9 @@ int save_autostash(const char *path);\n int save_autostash_ref(struct repository *r, const char *refname);\n int apply_autostash(const char *path);\n int apply_autostash_oid(const char *stash_oid);\n-int apply_autostash_ref(struct repository *r, const char *refname);\n+int apply_autostash_ref(struct repository *r, const char *refname,\n+\t\t\tconst char *label_ours, const char *label_theirs,\n+\t\t\tconst char *label_base, const char *stash_msg);\n \n #define SUMMARY_INITIAL_COMMIT   (1 << 0)\n #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)\n-- \ngitgitgadget\n\n"},{"id":"542448","messageId":"f35842408527a8c69f9936cdb127fb35efe2509c.1777401552.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"[PATCH v16 4/5] checkout: rollback lock on early returns in merge_working_tree","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T18:39:11Z","receivedAt":"2026-04-28T18:39:22Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nmerge_working_tree() acquires the index lock via\nrepo_hold_locked_index() but several early return paths exit\nwithout calling rollback_lock_file(), leaving the lock held.\nWhile this is currently harmless because the process exits soon\nafter, it becomes a problem if the function is ever called more\nthan once in the same process.\n\nAdd rollback_lock_file() calls to all early return paths.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/checkout.c | 29 ++++++++++++++++++++++-------\n 1 file changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex e031e61886..c80c62b37b 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -783,8 +783,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \tstruct tree *new_tree;\n \n \trepo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR);\n-\tif (repo_read_index_preload(the_repository, NULL, 0) < 0)\n+\tif (repo_read_index_preload(the_repository, NULL, 0) < 0) {\n+\t\trollback_lock_file(&lock_file);\n \t\treturn error(_(\"index file corrupt\"));\n+\t}\n \n \tresolve_undo_clear_index(the_repository->index);\n \tif (opts->new_orphan_branch && opts->orphan_from_empty_tree) {\n@@ -797,14 +799,18 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t} else {\n \t\tnew_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\tnew_branch_info->commit);\n-\t\tif (!new_tree)\n+\t\tif (!new_tree) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn error(_(\"unable to read tree (%s)\"),\n \t\t\t\t     oid_to_hex(&new_branch_info->commit->object.oid));\n+\t\t}\n \t}\n \tif (opts->discard_changes) {\n \t\tret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info);\n-\t\tif (ret)\n+\t\tif (ret) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\treturn ret;\n+\t\t}\n \t} else {\n \t\tstruct tree_desc trees[2];\n \t\tstruct tree *tree;\n@@ -814,6 +820,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\trefresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL);\n \n \t\tif (unmerged_index(the_repository->index)) {\n+\t\t\trollback_lock_file(&lock_file);\n \t\t\terror(_(\"you need to resolve your current index first\"));\n \t\t\treturn 1;\n \t\t}\n@@ -857,15 +864,19 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n \t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n \n-\t\t\tif (!opts->merge)\n+\t\t\tif (!opts->merge) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \n \t\t\t/*\n \t\t\t * Without old_branch_info->commit, the below is the same as\n \t\t\t * the two-tree unpack we already tried and failed.\n \t\t\t */\n-\t\t\tif (!old_branch_info->commit)\n+\t\t\tif (!old_branch_info->commit) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn 1;\n+\t\t\t}\n \t\t\told_tree = repo_get_commit_tree(the_repository,\n \t\t\t\t\t\t\told_branch_info->commit);\n \n@@ -897,8 +908,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\n \t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t\to.ancestor = old_branch_info->name;\n \t\t\tif (!old_branch_info->name) {\n \t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n@@ -920,8 +933,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t writeout_error, new_branch_info);\n \t\t\tstrbuf_release(&o.obuf);\n \t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret)\n+\t\t\tif (ret) {\n+\t\t\t\trollback_lock_file(&lock_file);\n \t\t\t\treturn ret;\n+\t\t\t}\n \t\t}\n \t}\n \n-- \ngitgitgadget\n\n"},{"id":"542449","messageId":"07d25fda91a1bbb3e13005d5462d1879303b15c0.1777401552.git.gitgitgadget@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"[PATCH v16 5/5] checkout -m: autostash when switching branches","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T18:39:12Z","receivedAt":"2026-04-28T18:39:24Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen switching branches with \"git checkout -m\", the attempted merge\nof local modifications may cause conflicts with the changes made on\nthe other branch, which the user may not want to (or may not be able\nto) resolve right now.  Because there is no easy way to recover from\nthis situation, we discouraged users from using \"checkout -m\" unless\nthey are certain their changes are trivial and within their ability\nto resolve conflicts.\n\nTeach the -m flow to create a temporary stash before switching and\nreapply it after.  On success, the stash is silently applied and\nthe list of locally modified paths is shown, same as a successful\n\"git checkout\" without \"-m\".\n\nIf reapplying causes conflicts, the stash is kept and the user is\ntold they can resolve and run \"git stash drop\", or run \"git reset\n--hard\" and later \"git stash pop\" to recover their changes.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/git-checkout.adoc |  55 ++++++-----\n Documentation/git-switch.adoc   |  36 +++----\n builtin/checkout.c              | 161 ++++++++++++++------------------\n sequencer.c                     |  14 ++-\n t/t3420-rebase-autostash.sh     |  16 ++--\n t/t7201-co.sh                   |  71 +++++++++++++-\n t/t7600-merge.sh                |   3 +-\n xdiff-interface.c               |  12 +++\n xdiff-interface.h               |   1 +\n 9 files changed, 219 insertions(+), 150 deletions(-)\n\ndiff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc\nindex 43ccf47cf6..a8b3b8c2e2 100644\n--- a/Documentation/git-checkout.adoc\n+++ b/Documentation/git-checkout.adoc\n@@ -251,20 +251,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.\n \tare different between the current branch and the branch to\n \twhich you are switching, the command refuses to switch\n \tbranches in order to preserve your modifications in context.\n-\tHowever, with this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch\n-\tis done, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tWith this option, the conflicting local changes are\n+\tautomatically stashed before the switch and reapplied\n+\tafterwards.  If the local changes do not overlap with the\n+\tdifferences between branches, the switch proceeds without\n+\tstashing.  If reapplying the stash results in conflicts, the\n+\tentry is saved to the stash list.  Resolve the conflicts\n+\tand run `git stash drop` when done, or clear the working\n+\ttree (e.g. with `git reset --hard`) before running `git stash\n+\tpop` later to re-apply your changes.\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.  This option cannot be\n used when checking out paths from a tree-ish.\n-+\n-When switching branches with `--merge`, staged changes may be lost.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -578,38 +577,36 @@ $ git checkout mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a\n-three-way merge:\n+You can give the `-m` flag to the command, which will carry your local\n+changes to the new branch:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n+Applied autostash.\n+Switched to branch 'mytopic'\n+The following paths have local changes:\n+M\tfrotz\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \n === 3. Merge conflict\n \n-When a merge conflict happens during switching branches with\n-the `-m` option, you would see something like this:\n+When the `--merge` (`-m`) option is given and the local changes\n+overlap with the changes in the branch we're switching to, the\n+changes are stashed and reapplied after the switch.  If this\n+process results in conflicts, the stash entry is saved and a\n+message is printed:\n \n ------------\n $ git checkout -m mytopic\n-Auto-merging frotz\n-ERROR: Merge conflict in frotz\n-fatal: merge program failed\n-------------\n-\n-At this point, `git diff` shows the changes cleanly merged as in\n-the previous example, as well as the changes in the conflicted\n-files.  Edit and resolve the conflict and mark it resolved with\n-`git add` as usual:\n-\n-------------\n-$ edit frotz\n-$ git add frotz\n+Your local changes are stashed, however applying them\n+resulted in conflicts.  You can either resolve the conflicts\n+and then discard the stash with \"git stash drop\", or, if you\n+do not want to resolve them now, run \"git reset --hard\" and\n+apply the local changes later by running \"git stash pop\".\n ------------\n \n CONFIGURATION\ndiff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc\nindex 87707e9265..d6c4f229a5 100644\n--- a/Documentation/git-switch.adoc\n+++ b/Documentation/git-switch.adoc\n@@ -123,18 +123,19 @@ variable.\n \n `-m`::\n `--merge`::\n-\tIf you have local modifications to one or more files that are\n-\tdifferent between the current branch and the branch to which\n-\tyou are switching, the command refuses to switch branches in\n-\torder to preserve your modifications in context.  However,\n-\twith this option, a three-way merge between the current\n-\tbranch, your working tree contents, and the new branch is\n-\tdone, and you will be on the new branch.\n-+\n-When a merge conflict happens, the index entries for conflicting\n-paths are left unmerged, and you need to resolve the conflicts\n-and mark the resolved paths with `git add` (or `git rm` if the merge\n-should result in deletion of the path).\n+\tIf you have local modifications to one or more files that\n+\tare different between the current branch and the branch to\n+\twhich you are switching, the command normally refuses to\n+\tswitch branches in order to preserve your modifications in\n+\tcontext.  However, with this option, the conflicting local\n+\tchanges are automatically stashed before the switch and\n+\treapplied afterwards.  If the local changes do not overlap\n+\twith the differences between branches, the switch proceeds\n+\twithout stashing.  If reapplying the stash results in\n+\tconflicts, the entry is saved to the stash list.  Resolve\n+\tthe conflicts and run `git stash drop` when done, or clear\n+\tthe working tree (e.g. with `git reset --hard`) before\n+\trunning `git stash pop` later to re-apply your changes.\n \n `--conflict=<style>`::\n \tThe same as `--merge` option above, but changes the way the\n@@ -217,15 +218,18 @@ $ git switch mytopic\n error: You have local changes to 'frotz'; not switching branches.\n ------------\n \n-You can give the `-m` flag to the command, which would try a three-way\n-merge:\n+You can give the `-m` flag to the command, which will carry your local\n+changes to the new branch:\n \n ------------\n $ git switch -m mytopic\n-Auto-merging frotz\n+Applied autostash.\n+Switched to branch 'mytopic'\n+The following paths have local changes:\n+M\tfrotz\n ------------\n \n-After this three-way merge, the local modifications are _not_\n+After the switch, the local modifications are reapplied and are _not_\n registered in your index file, so `git diff` would show you what\n changes you made since the tip of the new branch.\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex c80c62b37b..3e9c456fad 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -17,7 +17,6 @@\n #include \"merge-ll.h\"\n #include \"lockfile.h\"\n #include \"mem-pool.h\"\n-#include \"merge-ort-wrappers.h\"\n #include \"object-file.h\"\n #include \"object-name.h\"\n #include \"odb.h\"\n@@ -30,6 +29,7 @@\n #include \"repo-settings.h\"\n #include \"resolve-undo.h\"\n #include \"revision.h\"\n+#include \"sequencer.h\"\n #include \"setup.h\"\n #include \"submodule.h\"\n #include \"symlinks.h\"\n@@ -99,6 +99,8 @@ struct checkout_opts {\n \t.auto_advance = 1, \\\n }\n \n+#define MERGE_WORKING_TREE_UNPACK_FAILED (-2)\n+\n struct branch_info {\n \tchar *name; /* The short name used */\n \tchar *path; /* The full name of a real branch */\n@@ -753,9 +755,9 @@ static void setup_branch_path(struct branch_info *branch)\n \tbranch->path = strbuf_detach(&buf, NULL);\n }\n \n-static void init_topts(struct unpack_trees_options *topts, int merge,\n+static void init_topts(struct unpack_trees_options *topts,\n \t\t       int show_progress, int overwrite_ignore,\n-\t\t       struct commit *old_commit)\n+\t\t       bool quiet)\n {\n \tmemset(topts, 0, sizeof(*topts));\n \ttopts->head_idx = -1;\n@@ -767,7 +769,7 @@ static void init_topts(struct unpack_trees_options *topts, int merge,\n \ttopts->initial_checkout = is_index_unborn(the_repository->index);\n \ttopts->update = 1;\n \ttopts->merge = 1;\n-\ttopts->quiet = merge && old_commit;\n+\ttopts->quiet = quiet;\n \ttopts->verbose_update = show_progress;\n \ttopts->fn = twoway_merge;\n \ttopts->preserve_ignored = !overwrite_ignore;\n@@ -776,6 +778,7 @@ static void init_topts(struct unpack_trees_options *topts, int merge,\n static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t      struct branch_info *old_branch_info,\n \t\t\t      struct branch_info *new_branch_info,\n+\t\t\t      bool quiet,\n \t\t\t      int *writeout_error)\n {\n \tint ret;\n@@ -826,8 +829,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t}\n \n \t\t/* 2-way merge to the new branch */\n-\t\tinit_topts(&topts, opts->merge, opts->show_progress,\n-\t\t\t   opts->overwrite_ignore, old_branch_info->commit);\n+\t\tinit_topts(&topts, opts->show_progress,\n+\t\t\t   opts->overwrite_ignore, quiet);\n \t\tinit_checkout_metadata(&topts.meta, new_branch_info->refname,\n \t\t\t\t       new_branch_info->commit ?\n \t\t\t\t       &new_branch_info->commit->object.oid :\n@@ -853,90 +856,8 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\tret = unpack_trees(2, trees, &topts);\n \t\tclear_unpack_trees_porcelain(&topts);\n \t\tif (ret == -1) {\n-\t\t\t/*\n-\t\t\t * Unpack couldn't do a trivial merge; either\n-\t\t\t * give up or do a real merge, depending on\n-\t\t\t * whether the merge flag was used.\n-\t\t\t */\n-\t\t\tstruct tree *work;\n-\t\t\tstruct tree *old_tree;\n-\t\t\tstruct merge_options o;\n-\t\t\tstruct strbuf sb = STRBUF_INIT;\n-\t\t\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n-\n-\t\t\tif (!opts->merge) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\n-\t\t\t/*\n-\t\t\t * Without old_branch_info->commit, the below is the same as\n-\t\t\t * the two-tree unpack we already tried and failed.\n-\t\t\t */\n-\t\t\tif (!old_branch_info->commit) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn 1;\n-\t\t\t}\n-\t\t\told_tree = repo_get_commit_tree(the_repository,\n-\t\t\t\t\t\t\told_branch_info->commit);\n-\n-\t\t\tif (repo_index_has_changes(the_repository, old_tree, &sb))\n-\t\t\t\tdie(_(\"cannot continue with staged changes in \"\n-\t\t\t\t      \"the following files:\\n%s\"), sb.buf);\n-\t\t\tstrbuf_release(&sb);\n-\n-\t\t\t/* Do more real merge */\n-\n-\t\t\t/*\n-\t\t\t * We update the index fully, then write the\n-\t\t\t * tree from the index, then merge the new\n-\t\t\t * branch with the current tree, with the old\n-\t\t\t * branch as the base. Then we reset the index\n-\t\t\t * (but not the working tree) to the new\n-\t\t\t * branch, leaving the working tree as the\n-\t\t\t * merged version, but skipping unmerged\n-\t\t\t * entries in the index.\n-\t\t\t */\n-\n-\t\t\tadd_files_to_cache(the_repository, NULL, NULL, NULL, 0,\n-\t\t\t\t\t0, 0);\n-\t\t\tinit_ui_merge_options(&o, the_repository);\n-\t\t\to.verbosity = 0;\n-\t\t\twork = write_in_core_index_as_tree(the_repository,\n-\t\t\t\t\t\t\t   the_repository->index);\n-\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 1,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n-\t\t\to.ancestor = old_branch_info->name;\n-\t\t\tif (!old_branch_info->name) {\n-\t\t\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n-\t\t\t\t\t\t\t &old_branch_info->commit->object.oid,\n-\t\t\t\t\t\t\t DEFAULT_ABBREV);\n-\t\t\t\to.ancestor = old_commit_shortname.buf;\n-\t\t\t}\n-\t\t\to.branch1 = new_branch_info->name;\n-\t\t\to.branch2 = \"local\";\n-\t\t\to.conflict_style = opts->conflict_style;\n-\t\t\tret = merge_ort_nonrecursive(&o,\n-\t\t\t\t\t\t     new_tree,\n-\t\t\t\t\t\t     work,\n-\t\t\t\t\t\t     old_tree);\n-\t\t\tif (ret < 0)\n-\t\t\t\tdie(NULL);\n-\t\t\tret = reset_tree(new_tree,\n-\t\t\t\t\t opts, 0,\n-\t\t\t\t\t writeout_error, new_branch_info);\n-\t\t\tstrbuf_release(&o.obuf);\n-\t\t\tstrbuf_release(&old_commit_shortname);\n-\t\t\tif (ret) {\n-\t\t\t\trollback_lock_file(&lock_file);\n-\t\t\t\treturn ret;\n-\t\t\t}\n+\t\t\trollback_lock_file(&lock_file);\n+\t\t\treturn MERGE_WORKING_TREE_UNPACK_FAILED;\n \t\t}\n \t}\n \n@@ -1181,6 +1102,10 @@ static int switch_branches(const struct checkout_opts *opts,\n \tstruct object_id rev;\n \tint flag, writeout_error = 0;\n \tint do_merge = 1;\n+\tint created_autostash = 0;\n+\tstruct strbuf old_commit_shortname = STRBUF_INIT;\n+\tstruct strbuf autostash_msg = STRBUF_INIT;\n+\tconst char *stash_label_base = NULL;\n \n \ttrace2_cmd_mode(\"branch\");\n \n@@ -1218,11 +1143,49 @@ static int switch_branches(const struct checkout_opts *opts,\n \t\t\tdo_merge = 0;\n \t}\n \n+\tif (old_branch_info.name) {\n+\t\tstash_label_base = old_branch_info.name;\n+\t} else if (old_branch_info.commit) {\n+\t\tstrbuf_add_unique_abbrev(&old_commit_shortname,\n+\t\t\t\t\t &old_branch_info.commit->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tstash_label_base = old_commit_shortname.buf;\n+\t}\n+\n \tif (do_merge) {\n-\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n+\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n+\t\t\t\t\t opts->merge, &writeout_error);\n+\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n+\t\t\tstrbuf_addf(&autostash_msg,\n+\t\t\t\t    \"autostash while switching to '%s'\",\n+\t\t\t\t    new_branch_info->name);\n+\t\t\tcreate_autostash_ref(the_repository,\n+\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t     autostash_msg.buf, true);\n+\t\t\tcreated_autostash = 1;\n+\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n+\t\t\t\t\t\t false, &writeout_error);\n+\t\t}\n+\t\tif (created_autostash) {\n+\t\t\tif (opts->conflict_style >= 0) {\n+\t\t\t\tstruct strbuf cfg = STRBUF_INIT;\n+\t\t\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n+\t\t\t\t\t    conflict_style_name(opts->conflict_style));\n+\t\t\t\tgit_config_push_parameter(cfg.buf);\n+\t\t\t\tstrbuf_release(&cfg);\n+\t\t\t}\n+\t\t\tapply_autostash_ref(the_repository,\n+\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n+\t\t\t\t\t    new_branch_info->name,\n+\t\t\t\t\t    \"local\",\n+\t\t\t\t\t    stash_label_base,\n+\t\t\t\t\t    autostash_msg.buf);\n+\t\t}\n \t\tif (ret) {\n \t\t\tbranch_info_release(&old_branch_info);\n-\t\t\treturn ret;\n+\t\t\tstrbuf_release(&old_commit_shortname);\n+\t\t\tstrbuf_release(&autostash_msg);\n+\t\t\treturn ret < 0 ? 1 : ret;\n \t\t}\n \t}\n \n@@ -1231,8 +1194,22 @@ static int switch_branches(const struct checkout_opts *opts,\n \n \tupdate_refs_for_switch(opts, &old_branch_info, new_branch_info);\n \n+\tif (created_autostash) {\n+\t\tdiscard_index(the_repository->index);\n+\t\tif (repo_read_index(the_repository) < 0)\n+\t\t\tdie(_(\"index file corrupt\"));\n+\n+\t\tif (!opts->quiet && new_branch_info->commit) {\n+\t\t\tprintf(_(\"The following paths have local changes:\\n\"));\n+\t\t\tshow_local_changes(&new_branch_info->commit->object,\n+\t\t\t\t\t   &opts->diff_options);\n+\t\t}\n+\t}\n+\n \tret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);\n \tbranch_info_release(&old_branch_info);\n+\tstrbuf_release(&old_commit_shortname);\n+\tstrbuf_release(&autostash_msg);\n \n \treturn ret || writeout_error;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 7c0376d9e4..746f85a442 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4765,15 +4765,19 @@ static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply,\n \t\tstrvec_push(&store.args, stash_oid);\n \t\tif (run_command(&store))\n \t\t\tret = error(_(\"cannot store %s\"), stash_oid);\n+\t\telse if (attempt_apply)\n+\t\t\tfprintf(stderr,\n+\t\t\t\t_(\"Your local changes are stashed, however applying them\\n\"\n+\t\t\t\t  \"resulted in conflicts.  You can either resolve the conflicts\\n\"\n+\t\t\t\t  \"and then discard the stash with \\\"git stash drop\\\", or, if you\\n\"\n+\t\t\t\t  \"do not want to resolve them now, run \\\"git reset --hard\\\" and\\n\"\n+\t\t\t\t  \"apply the local changes later by running \\\"git stash pop\\\".\\n\"));\n \t\telse\n \t\t\tfprintf(stderr,\n-\t\t\t\t_(\"%s\\n\"\n+\t\t\t\t_(\"Autostash exists; creating a new stash entry.\\n\"\n \t\t\t\t  \"Your changes are safe in the stash.\\n\"\n \t\t\t\t  \"You can run \\\"git stash pop\\\" or\"\n-\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"),\n-\t\t\t\tattempt_apply ?\n-\t\t\t\t_(\"Applying autostash resulted in conflicts.\") :\n-\t\t\t\t_(\"Autostash exists; creating a new stash entry.\"));\n+\t\t\t\t  \" \\\"git stash drop\\\" at any time.\\n\"));\n \t}\n \n \treturn ret;\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex ad3ba6a984..f0bbc476ff 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -61,18 +61,22 @@ create_expected_failure_apply () {\n \tFirst, rewinding head to replay your work on top of it...\n \tApplying: second commit\n \tApplying: third commit\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however applying them\n+\tresulted in conflicts.  You can either resolve the conflicts\n+\tand then discard the stash with \"git stash drop\", or, if you\n+\tdo not want to resolve them now, run \"git reset --hard\" and\n+\tapply the local changes later by running \"git stash pop\".\n \tEOF\n }\n \n create_expected_failure_merge () {\n \tcat >expected <<-EOF\n \t$(grep \"^Created autostash: [0-9a-f][0-9a-f]*\\$\" actual)\n-\tApplying autostash resulted in conflicts.\n-\tYour changes are safe in the stash.\n-\tYou can run \"git stash pop\" or \"git stash drop\" at any time.\n+\tYour local changes are stashed, however applying them\n+\tresulted in conflicts.  You can either resolve the conflicts\n+\tand then discard the stash with \"git stash drop\", or, if you\n+\tdo not want to resolve them now, run \"git reset --hard\" and\n+\tapply the local changes later by running \"git stash pop\".\n \tSuccessfully rebased and updated refs/heads/rebased-feature-branch.\n \tEOF\n }\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 9bcf7c0b40..7613b1d2a4 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -102,7 +102,10 @@ test_expect_success 'checkout -m with dirty tree' '\n \n \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n \n-\tprintf \"M\\t%s\\n\" one >expect.messages &&\n+\tcat >expect.messages <<-\\EOF &&\n+\tThe following paths have local changes:\n+\tM\tone\n+\tEOF\n \ttest_cmp expect.messages messages &&\n \n \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n@@ -210,6 +213,72 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' '\n \ttest_cmp expect two\n '\n \n+test_expect_success 'checkout -m with mixed staged and unstaged changes' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 0 x y z >same &&\n+\tgit add same &&\n+\tfill 1 2 3 4 5 6 7 >one &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"Applied autostash\" actual &&\n+\tfill 0 x y z >expect &&\n+\ttest_cmp expect same &&\n+\tfill 1 2 3 4 5 6 7 >expect &&\n+\ttest_cmp expect one\n+'\n+\n+test_expect_success 'checkout -m creates a recoverable stash on conflict' '\n+\tgit checkout -f main &&\n+\tgit clean -f &&\n+\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout side 2>stderr &&\n+\ttest_grep \"Your local changes\" stderr &&\n+\tgit checkout -m side >actual 2>&1 &&\n+\ttest_grep \"resulted in conflicts\" actual &&\n+\ttest_grep \"git stash drop\" actual &&\n+\ttest_grep \"git stash pop\" actual &&\n+\ttest_grep \"The following paths have local changes\" actual &&\n+\tgit log -p -1 --format=\"%gs%n%B\" -g --diff-merges=1 refs/stash >actual &&\n+\tsed /^index/d actual >actual.trimmed &&\n+\tcat >expect <<-EOF &&\n+\tautostash while switching to ${SQ}side${SQ}\n+\tOn main: autostash while switching to ${SQ}side${SQ}\n+\n+\tdiff --git a/one b/one\n+\t--- a/one\n+\t+++ b/one\n+\t@@ -3,6 +3,3 @@\n+\t 3\n+\t 4\n+\t 5\n+\t-6\n+\t-7\n+\t-8\n+\tEOF\n+\ttest_cmp expect actual.trimmed &&\n+\tgit stash drop &&\n+\tgit reset --hard\n+'\n+\n+test_expect_success 'checkout -m which would overwrite untracked file' '\n+\tgit checkout -f --detach main &&\n+\ttest_commit another-file &&\n+\tgit checkout HEAD^ &&\n+\t>another-file.t &&\n+\tfill 1 2 3 4 5 >one &&\n+\ttest_must_fail git checkout -m @{-1} 2>err &&\n+\tq_to_tab >expect <<-\\EOF &&\n+\terror: The following untracked working tree files would be overwritten by checkout:\n+\tQanother-file.t\n+\tPlease move or remove them before you switch branches.\n+\tAborting\n+\tApplied autostash.\n+\tEOF\n+\ttest_cmp expect err\n+'\n+\n test_expect_success 'switch to another branch while carrying a deletion' '\n \tgit checkout -f main &&\n \tgit reset --hard &&\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 9838094b66..f877d9a433 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -914,7 +914,8 @@ test_expect_success 'merge with conflicted --autostash changes' '\n \tgit diff >expect &&\n \ttest_when_finished \"test_might_fail git stash drop\" &&\n \tgit merge --autostash c3 2>err &&\n-\ttest_grep \"Applying autostash resulted in conflicts.\" err &&\n+\ttest_grep \"applying them\" err &&\n+\ttest_grep \"resulted in conflicts\" err &&\n \tgit show HEAD:file >merge-result &&\n \ttest_cmp result.1-9 merge-result &&\n \tgit stash show -p >actual &&\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex f043330f2a..5ee2b96d0a 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -325,6 +325,18 @@ int parse_conflict_style_name(const char *value)\n \t\treturn -1;\n }\n \n+const char *conflict_style_name(int style)\n+{\n+\tswitch (style) {\n+\tcase XDL_MERGE_DIFF3:\n+\t\treturn \"diff3\";\n+\tcase XDL_MERGE_ZEALOUS_DIFF3:\n+\t\treturn \"zdiff3\";\n+\tdefault:\n+\t\treturn \"merge\";\n+\t}\n+}\n+\n int git_xmerge_style = -1;\n \n int git_xmerge_config(const char *var, const char *value,\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex fbc4ceec40..ce54e1c0e0 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -55,6 +55,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n struct config_context;\n int parse_conflict_style_name(const char *value);\n+const char *conflict_style_name(int style);\n int git_xmerge_config(const char *var, const char *value,\n \t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n"},{"id":"542466","messageId":"cfd09dbf-8d77-4464-8030-3a0ffb4aeae7@gmail.com","threadId":"65220","inReplyTo":"07d25fda91a1bbb3e13005d5462d1879303b15c0.1777401552.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v16 5/5] checkout -m: autostash when switching branches","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-29T10:02:17Z","receivedAt":"2026-04-29T10:02:56Z","isPatch":true,"body":"Hi Harald\n\nOn 28/04/2026 19:39, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n>   \tif (do_merge) {\n> -\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n> +\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n> +\t\t\t\t\t opts->merge, &writeout_error);\n> +\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n> +\t\t\tstrbuf_addf(&autostash_msg,\n> +\t\t\t\t    \"autostash while switching to '%s'\",\n> +\t\t\t\t    new_branch_info->name);\n> +\t\t\tcreate_autostash_ref(the_repository,\n> +\t\t\t\t\t     \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t     autostash_msg.buf, true);\n\nIf there are no local changes then doing this is pointless - it means \nunpack_trees() failed for another reason. Having said that the current \ncode also tries a 3-way merge unconditionally so I think we can happily \nleave this for the future as #leftoverbits\n\n> +\t\t\tcreated_autostash = 1;\n> +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n> +\t\t\t\t\t\t false, &writeout_error);\n> +\t\t}\n> +\t\tif (created_autostash) {\n> +\t\t\tif (opts->conflict_style >= 0) {\n> +\t\t\t\tstruct strbuf cfg = STRBUF_INIT;\n> +\t\t\t\tstrbuf_addf(&cfg, \"merge.conflictStyle=%s\",\n> +\t\t\t\t\t    conflict_style_name(opts->conflict_style));\n> +\t\t\t\tgit_config_push_parameter(cfg.buf);\n> +\t\t\t\tstrbuf_release(&cfg);\n> +\t\t\t}\n> +\t\t\tapply_autostash_ref(the_repository,\n> +\t\t\t\t\t    \"CHECKOUT_AUTOSTASH_HEAD\",\n> +\t\t\t\t\t    new_branch_info->name,\n> +\t\t\t\t\t    \"local\",\n> +\t\t\t\t\t    stash_label_base,\n> +\t\t\t\t\t    autostash_msg.buf);\n> +\t\t}\n>   \t\tif (ret) {\n>   \t\t\tbranch_info_release(&old_branch_info);\n> -\t\t\treturn ret;\n> +\t\t\tstrbuf_release(&old_commit_shortname);\n> +\t\t\tstrbuf_release(&autostash_msg);\n> +\t\t\treturn ret < 0 ? 1 : ret;\n>   \t\t}\n>   \t}\n\nIf popping the stash created merge conflicts then it would be nice to \nprint a blank line before the message about which branch we've switched \nto so that it is visually separated from the conflicts advice. That \nwould mean apply_autostash_ref() would have to tell us if there we're \nconflicts. Again we can happily leave that for the future as #leftoverbits\n\nThanks\n\nPhillip\n\n"},{"id":"542467","messageId":"3170928a-e05e-42cc-b15e-56e0fa479066@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"Re: [PATCH v16 0/5] checkout: 'autostash' for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-29T10:02:44Z","receivedAt":"2026-04-29T10:02:57Z","isPatch":true,"body":"Hi Harald\n\nOn 28/04/2026 19:39, Harald Nordgren via GitGitGadget wrote:\n>   * Updated the git checkout and git switch docs to show the actual output\n>     when using -m to carry local changes across a branch switch, and changed\n>     \"would carry\" to \"will carry\".\n>   * Rewrote the merge-conflict example in both docs to match the new, more\n>     concise message printed on autostash conflicts.\n>   * Replaced the show_unpack_errors flag and old_commit parameter in\n>     merge_working_tree()/init_topts() with a plain quiet boolean, so the\n>     caller decides directly whether to suppress unpack errors.\n>   * Tightened the 'checkout -m with dirty tree' test by replacing a printf\n>     with a heredoc.\n>   * Made the 'checkout -m creates a recoverable stash on conflict' test also\n>     assert the reflog subject of the new stash entry.\n>   * Replaced two test_grep calls in the 'checkout -m which would overwrite\n>     untracked file' test with a single test_cmp, which also catches a\n>     regression where the \"would be overwritten\" message could end up printed\n>     twice.\n\nThat all sounds good and the range-diff below looks as I would expect it \nto. I've left some suggestions for possible future work on patch 5 but I \nthink this is ready to be merged as-is.\n\nThanks for working on it\n\nPhillip\n\n> Harald Nordgren (5):\n>    stash: add --label-ours, --label-theirs, --label-base for apply\n>    sequencer: allow create_autostash to run silently\n>    sequencer: teach autostash apply to take optional conflict marker\n>      labels\n>    checkout: rollback lock on early returns in merge_working_tree\n>    checkout -m: autostash when switching branches\n> \n>   Documentation/git-checkout.adoc |  55 +++++------\n>   Documentation/git-stash.adoc    |  11 ++-\n>   Documentation/git-switch.adoc   |  36 ++++---\n>   builtin/checkout.c              | 166 +++++++++++++++-----------------\n>   builtin/commit.c                |   3 +-\n>   builtin/merge.c                 |  15 ++-\n>   builtin/stash.c                 |  28 ++++--\n>   sequencer.c                     |  69 +++++++++----\n>   sequencer.h                     |   7 +-\n>   t/t3420-rebase-autostash.sh     |  16 +--\n>   t/t3903-stash.sh                |  24 +++++\n>   t/t7201-co.sh                   |  71 +++++++++++++-\n>   t/t7600-merge.sh                |   3 +-\n>   xdiff-interface.c               |  12 +++\n>   xdiff-interface.h               |   1 +\n>   xdiff/xmerge.c                  |   6 +-\n>   16 files changed, 343 insertions(+), 180 deletions(-)\n> \n> \n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v16\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v16\n> Pull-Request: https://github.com/git/git/pull/2234\n> \n> Range-diff vs v15:\n> \n>   1:  aba8e6a9dc = 1:  aba8e6a9dc stash: add --label-ours, --label-theirs, --label-base for apply\n>   2:  89e0bfa803 = 2:  89e0bfa803 sequencer: allow create_autostash to run silently\n>   3:  a428ce7328 = 3:  a428ce7328 sequencer: teach autostash apply to take optional conflict marker labels\n>   4:  f358424085 = 4:  f358424085 checkout: rollback lock on early returns in merge_working_tree\n>   5:  96b14db827 ! 5:  07d25fda91 checkout -m: autostash when switching branches\n>       @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n>         \n>        -You can give the `-m` flag to the command, which would try a\n>        -three-way merge:\n>       -+You can give the `-m` flag to the command, which would carry your local\n>       ++You can give the `-m` flag to the command, which will carry your local\n>        +changes to the new branch:\n>         \n>         ------------\n>         $ git checkout -m mytopic\n>        -Auto-merging frotz\n>       ++Applied autostash.\n>        +Switched to branch 'mytopic'\n>       ++The following paths have local changes:\n>       ++M\tfrotz\n>         ------------\n>         \n>        -After this three-way merge, the local modifications are _not_\n>       @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n>         \n>        -When a merge conflict happens during switching branches with\n>        -the `-m` option, you would see something like this:\n>       -+When the `--merge` (`-m`) option is in effect and the locally\n>       -+modified files overlap with files that need to be updated by the\n>       -+branch switch, the changes are stashed and reapplied after the\n>       -+switch.  If this process results in conflicts, a stash entry is saved\n>       -+and made available in `git stash list`:\n>       ++When the `--merge` (`-m`) option is given and the local changes\n>       ++overlap with the changes in the branch we're switching to, the\n>       ++changes are stashed and reapplied after the switch.  If this\n>       ++process results in conflicts, the stash entry is saved and a\n>       ++message is printed:\n>         \n>         ------------\n>         $ git checkout -m mytopic\n>       @@ Documentation/git-checkout.adoc: $ git checkout mytopic\n>        -ERROR: Merge conflict in frotz\n>        -fatal: merge program failed\n>        -------------\n>       -+Your local changes are stashed, however, applying it to carry\n>       -+forward your local changes resulted in conflicts:\n>       -\n>       +-\n>        -At this point, `git diff` shows the changes cleanly merged as in\n>        -the previous example, as well as the changes in the conflicted\n>        -files.  Edit and resolve the conflict and mark it resolved with\n>        -`git add` as usual:\n>       -+ - You can try resolving them now.  If you resolved them\n>       -+   successfully, discard the stash entry with \"git stash drop\".\n>       -\n>       -+ - Alternatively you can \"git reset --hard\" if you do not want\n>       -+   to deal with them right now, and later \"git stash pop\" to\n>       -+   recover your local changes.\n>       - ------------\n>       +-\n>       +-------------\n>        -$ edit frotz\n>        -$ git add frotz\n>       --------------\n>       -+\n>       -+You can try resolving the conflicts now.  Edit the conflicting files\n>       -+and mark them resolved with `git add` as usual, then run `git stash\n>       -+drop` to discard the stash entry.  Alternatively, you can clear the\n>       -+working tree with `git reset --hard` and recover your local changes\n>       -+later with `git stash pop`.\n>       ++Your local changes are stashed, however applying them\n>       ++resulted in conflicts.  You can either resolve the conflicts\n>       ++and then discard the stash with \"git stash drop\", or, if you\n>       ++do not want to resolve them now, run \"git reset --hard\" and\n>       ++apply the local changes later by running \"git stash pop\".\n>       + ------------\n>         \n>         CONFIGURATION\n>       - -------------\n>        \n>         ## Documentation/git-switch.adoc ##\n>        @@ Documentation/git-switch.adoc: variable.\n>       @@ Documentation/git-switch.adoc: $ git switch mytopic\n>         \n>        -You can give the `-m` flag to the command, which would try a three-way\n>        -merge:\n>       -+You can give the `-m` flag to the command, which would carry your local\n>       ++You can give the `-m` flag to the command, which will carry your local\n>        +changes to the new branch:\n>         \n>         ------------\n>         $ git switch -m mytopic\n>        -Auto-merging frotz\n>       ++Applied autostash.\n>        +Switched to branch 'mytopic'\n>       ++The following paths have local changes:\n>       ++M\tfrotz\n>         ------------\n>         \n>        -After this three-way merge, the local modifications are _not_\n>       @@ builtin/checkout.c: struct checkout_opts {\n>         \tchar *name; /* The short name used */\n>         \tchar *path; /* The full name of a real branch */\n>        @@ builtin/checkout.c: static void setup_branch_path(struct branch_info *branch)\n>       + \tbranch->path = strbuf_detach(&buf, NULL);\n>       + }\n>         \n>       - static void init_topts(struct unpack_trees_options *topts, int merge,\n>       +-static void init_topts(struct unpack_trees_options *topts, int merge,\n>       ++static void init_topts(struct unpack_trees_options *topts,\n>         \t\t       int show_progress, int overwrite_ignore,\n>        -\t\t       struct commit *old_commit)\n>       -+\t\t       struct commit *old_commit, bool show_unpack_errors)\n>       ++\t\t       bool quiet)\n>         {\n>         \tmemset(topts, 0, sizeof(*topts));\n>         \ttopts->head_idx = -1;\n>       @@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i\n>         \ttopts->update = 1;\n>         \ttopts->merge = 1;\n>        -\ttopts->quiet = merge && old_commit;\n>       -+\ttopts->quiet = merge && old_commit && !show_unpack_errors;\n>       ++\ttopts->quiet = quiet;\n>         \ttopts->verbose_update = show_progress;\n>         \ttopts->fn = twoway_merge;\n>         \ttopts->preserve_ignored = !overwrite_ignore;\n>       @@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i\n>         static int merge_working_tree(const struct checkout_opts *opts,\n>         \t\t\t      struct branch_info *old_branch_info,\n>         \t\t\t      struct branch_info *new_branch_info,\n>       --\t\t\t      int *writeout_error)\n>       -+\t\t\t      int *writeout_error,\n>       -+\t\t\t      bool show_unpack_errors)\n>       ++\t\t\t      bool quiet,\n>       + \t\t\t      int *writeout_error)\n>         {\n>         \tint ret;\n>       - \tstruct lock_file lock_file = LOCK_INIT;\n>        @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,\n>       + \t\t}\n>         \n>         \t\t/* 2-way merge to the new branch */\n>       - \t\tinit_topts(&topts, opts->merge, opts->show_progress,\n>       +-\t\tinit_topts(&topts, opts->merge, opts->show_progress,\n>        -\t\t\t   opts->overwrite_ignore, old_branch_info->commit);\n>       -+\t\t\t   opts->overwrite_ignore, old_branch_info->commit,\n>       -+\t\t\t   show_unpack_errors);\n>       ++\t\tinit_topts(&topts, opts->show_progress,\n>       ++\t\t\t   opts->overwrite_ignore, quiet);\n>         \t\tinit_checkout_metadata(&topts.meta, new_branch_info->refname,\n>         \t\t\t\t       new_branch_info->commit ?\n>         \t\t\t\t       &new_branch_info->commit->object.oid :\n>       @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>         \tif (do_merge) {\n>        -\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);\n>        +\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n>       -+\t\t\t\t\t &writeout_error, false);\n>       ++\t\t\t\t\t opts->merge, &writeout_error);\n>        +\t\tif (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {\n>        +\t\t\tstrbuf_addf(&autostash_msg,\n>        +\t\t\t\t    \"autostash while switching to '%s'\",\n>       @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n>        +\t\t\t\t\t     autostash_msg.buf, true);\n>        +\t\t\tcreated_autostash = 1;\n>        +\t\t\tret = merge_working_tree(opts, &old_branch_info, new_branch_info,\n>       -+\t\t\t\t\t\t &writeout_error, true);\n>       ++\t\t\t\t\t\t false, &writeout_error);\n>        +\t\t}\n>        +\t\tif (created_autostash) {\n>        +\t\t\tif (opts->conflict_style >= 0) {\n>       @@ t/t7201-co.sh: test_expect_success 'checkout -m with dirty tree' '\n>         \ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/side\" &&\n>         \n>        -\tprintf \"M\\t%s\\n\" one >expect.messages &&\n>       -+\tprintf \"The following paths have local changes:\\nM\\t%s\\n\" one >expect.messages &&\n>       ++\tcat >expect.messages <<-\\EOF &&\n>       ++\tThe following paths have local changes:\n>       ++\tM\tone\n>       ++\tEOF\n>         \ttest_cmp expect.messages messages &&\n>         \n>         \tfill \"M\tone\" \"A\tthree\" \"D\ttwo\" >expect.main &&\n>       @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n>        +\ttest_grep \"git stash drop\" actual &&\n>        +\ttest_grep \"git stash pop\" actual &&\n>        +\ttest_grep \"The following paths have local changes\" actual &&\n>       -+\tgit show --format=%B --diff-merges=1 refs/stash >actual &&\n>       ++\tgit log -p -1 --format=\"%gs%n%B\" -g --diff-merges=1 refs/stash >actual &&\n>        +\tsed /^index/d actual >actual.trimmed &&\n>        +\tcat >expect <<-EOF &&\n>       ++\tautostash while switching to ${SQ}side${SQ}\n>        +\tOn main: autostash while switching to ${SQ}side${SQ}\n>        +\n>        +\tdiff --git a/one b/one\n>       @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'\n>        +\t>another-file.t &&\n>        +\tfill 1 2 3 4 5 >one &&\n>        +\ttest_must_fail git checkout -m @{-1} 2>err &&\n>       -+\ttest_grep \"would be overwritten by checkout\" err &&\n>       -+\ttest_grep \"another-file.t\" err\n>       ++\tq_to_tab >expect <<-\\EOF &&\n>       ++\terror: The following untracked working tree files would be overwritten by checkout:\n>       ++\tQanother-file.t\n>       ++\tPlease move or remove them before you switch branches.\n>       ++\tAborting\n>       ++\tApplied autostash.\n>       ++\tEOF\n>       ++\ttest_cmp expect err\n>        +'\n>        +\n>         test_expect_success 'switch to another branch while carrying a deletion' '\n> \n\n"},{"id":"542469","messageId":"20260429111152.40619-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"3170928a-e05e-42cc-b15e-56e0fa479066@gmail.com","subject":"[PATCH] checkout: add --autostash option for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-29T11:11:52Z","receivedAt":"2026-04-29T11:11:55Z","isPatch":true,"body":"> That all sounds good and the range-diff below looks as I would expect it\n> to. I've left some suggestions for possible future work on patch 5 but I\n> think this is ready to be merged as-is.\n> \n> Thanks for working on it\n\nThank you too!\n\n\nHarald\n"},{"id":"542875","messageId":"20260507201112.41110-1-haraldnordgren@gmail.com","threadId":"65220","inReplyTo":"pull.2234.v16.git.git.1777401552.gitgitgadget@gmail.com","subject":"[PATCH v16 0/5] checkout: 'autostash' for branch switching","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-05-07T20:11:12Z","receivedAt":"2026-05-07T20:11:15Z","isPatch":true,"body":"Is this ready to be merged?\n\n\nHarald\n"},{"id":"542915","messageId":"852a731f-ae6a-465a-9af3-9140adb4fa01@gmail.com","threadId":"65220","inReplyTo":"20260507201112.41110-1-haraldnordgren@gmail.com","subject":"Re: [PATCH v16 0/5] checkout: 'autostash' for branch switching","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-08T13:02:20Z","receivedAt":"2026-05-08T13:02:14Z","isPatch":true,"body":"Hi Harald\n\nOn 07/05/2026 21:11, Harald Nordgren wrote:\n > Is this ready to be merged?\n\nYou can see the current status by reading the latest what's cooking \nemail from Junio. I don't think he's officially back online until next \nweek so I wouldn't be surprised if this hasn't been merged yet. You can \nalways check\n\n\tgit log --merges --grep hn/ origin/next\n\nto see which of your topics are in next.\n\nThanks\n\nPhillip\n\n\n"}]}