From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-oa1-f46.google.com (mail-oa1-f46.google.com [209.85.160.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 06D6C1BD9CE for ; Sat, 14 Mar 2026 09:59:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.46 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773482384; cv=none; b=XpY0QHNmiBaaBekC3ziHOjn4AiKaUdQDUij8FoFe+THRQdSa8Tm6Ng6sFzpB/iCAV1EItty6KNPu0HEkpegIJU9N3Iuaja/Hia4lVUS/2/SmyvUuCCAbP38rEi4khweAJLyjT9o9lWy/kWHpBReCahA09LPiz4QHvYnrF2nDQnQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773482384; c=relaxed/simple; bh=WKunKtx6ktS3rMA5BSQJ0JayuBh/1i1Ba58xPy05qhU=; h=Message-Id:In-Reply-To:References:From:Date:Subject:Content-Type: MIME-Version:To:Cc; b=Z3la0nIaSomoJV3Kx9SRdJgLxu8dRDWFObiBuY3cuoRqNdxZhZ9GB3F4p/Da+vvD3g3x/oGRUcYM15ME3a79b9638fYm/q5Mo2Zk4PrNr0u9Iv0YpyolzSI9nqnXnqWc+7hGbj7Ng6kzmaKzfTQDbcXwLm01pXiK7wdYhUDyaHw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=dRHxpUcW; arc=none smtp.client-ip=209.85.160.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dRHxpUcW" Received: by mail-oa1-f46.google.com with SMTP id 586e51a60fabf-40f387a688dso2418999fac.0 for ; Sat, 14 Mar 2026 02:59:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773482379; x=1774087179; darn=vger.kernel.org; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:from:to:cc:subject:date :message-id:reply-to; bh=yx962H0z9gOOXTzTEYCaNJxpV8T4y4JpvJTxHsFDg9Q=; b=dRHxpUcWG1mtrSCgXiUH4H/ZLfH2+SK29AoDMzc6waEjsuCYdMx66C1/vISLSr9AN4 goqmmyyKbuwKVbwNdRtyv5p3o+oH1HHMGDQyLBaZ+KlQ0+HdPvjfdhbfGWAYXCqrl4up rptvu6wdMZ41ZFOkfIbOeUznlFDlGlyRErr9JndH/mw+Jse+7x5hQlTcBjWl9lFqQXBW 4wvZglqmwyL3JxlOAil+6YX5KJGnl8FnrghXJwCLjwCyo0KwejDPwVee/oZwDRh0pbs+ +hw53nQZrvLPn0I+LoOealI9DV/2wCTqd6TXhdjnZC+ETntyUYDNFirurLdfC0Ht9Nx0 FdIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773482379; x=1774087179; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date:from :references:in-reply-to:message-id:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=yx962H0z9gOOXTzTEYCaNJxpV8T4y4JpvJTxHsFDg9Q=; b=a+EKDR/S22S17vguxmDDuv7j76YXx+PgzWOecyzoJ6hIVYS6XxVS82Hi9q2cu3OCjU kmoNi+3ALxn508U2C2Flc2GQKIeZax6NxmmBRcayzOO0og4OFGulVzipGRCx64mc833k NNDEpCe1yHFw8LfaViAybsEvjYpg8G2zp7wLOjPLaMYcMdQCVcwfW1RjpXj4Zf4sok4f HujCidlwZZ07bg2jHko3VGOdNvyy4zLDLW2QX4mVPNxX/GFK91BvnJG3srD4gm5HWc8L yI3d9NQrWte9bZV+bqZ+H+ruIC9KE0BINgfGuul9Do2iPs6HiB5mYiDw/NP/EkEzVEFD QEHw== X-Gm-Message-State: AOJu0YzAOOWGurq89EDrekvPTCCIc/a8RH5XHF7QzB62EICCLUwsQspt GzZgEGfOQzfJ8VFYQyUXPykpx1BvaK1xbSqJw0xphFj0YF58xdnH6La21VfCNA== X-Gm-Gg: ATEYQzyRb+mryYHQPeV3Qf0Enzo66KFMYWUlqR7pzoBXFMxJUFCt/t0P1GUI6OXj+VQ 97LAhITqtNRGipUyr1esEwEv/Dru1Nkzbk1qnd5U1FV+rll0JFNMm/wO+3mCVvoIu6AbRhmmrXr KArsvEB//wUo9n4alr6MP/da2niNQuXT9rxJYBFtgIrjN8OKWsIdB98accPPznqqpJeewlWe17X IA+RXiGCyuaHfxi0EMPxY0loiF/Q3q4W0PedqpKv5kriak06W/0ALG/0V3hvc5fkx4R0wKNoBzC 1/WnEkD+Dv48xAwfpWUlsjN/4vjfd9rGKHcymzFJdW3pZ/LlELsGIWkTBygOcWqMVPJJItIwmAp jMMXEwzMV9/6c16ZogNTC3NhtTQu948KhUHSh03iKy5lbfeJ6JwC6cJJ3y1XHoWs9DK1pezycQj skKtVFj7pw1Cvg9MU+l26TWuWSOg== X-Received: by 2002:a05:6871:e708:b0:40e:95b9:40f3 with SMTP id 586e51a60fabf-417b946c48fmr3980608fac.39.1773482378868; Sat, 14 Mar 2026 02:59:38 -0700 (PDT) Received: from [127.0.0.1] ([52.173.219.146]) by smtp.gmail.com with ESMTPSA id 586e51a60fabf-4177e5e94b4sm9928243fac.11.2026.03.14.02.59.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 14 Mar 2026 02:59:37 -0700 (PDT) Message-Id: In-Reply-To: References: From: "Harald Nordgren via GitGitGadget" Date: Sat, 14 Mar 2026 09:59:35 +0000 Subject: [PATCH v4] checkout: -m (--merge) uses autostash when switching branches Fcc: Sent Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 To: git@vger.kernel.org Cc: Harald Nordgren , Harald Nordgren From: Harald Nordgren When switching branches with "git checkout -m", local modifications can block the switch. Teach the -m flow to create a temporary stash before switching and reapply it after. On success, only "Applied autostash." is shown. If reapplying causes conflicts, the stash is kept and the user is told they can resolve and run "git stash drop", or run "git reset --hard" and later "git stash pop" to recover their changes. Signed-off-by: Harald Nordgren --- checkout: 'autostash' for branch switching cc: Phillip Wood phillip.wood123@gmail.com Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v4 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v4 Pull-Request: https://github.com/git/git/pull/2234 Range-diff vs v3: 1: 05f1e53163 ! 1: 5d49c0031a checkout: add --autostash option for branch switching @@ Metadata Author: Harald Nordgren ## Commit message ## - checkout: add --autostash option for branch switching + checkout: -m (--merge) uses autostash when switching branches - When switching branches, local modifications in the working tree can - prevent the checkout from succeeding. While "git rebase" and "git - merge" already support --autostash to handle this case automatically, - "git checkout" and "git switch" require users to manually stash and - unstash their changes. - - Teach "git checkout" and "git switch" to accept --autostash and - --no-autostash options that automatically create a temporary stash - entry before the branch switch begins and apply it after the switch - completes. If the stash application results in conflicts, the stash - entry is saved to the stash list so the user can resolve them later. - - Also add a checkout.autoStash configuration option that enables this - behavior by default, which can be overridden with --no-autostash on - the command line. + When switching branches with "git checkout -m", local modifications + can block the switch. Teach the -m flow to create a temporary stash + before switching and reapply it after. On success, only "Applied + autostash." is shown. If reapplying causes conflicts, the stash is + kept and the user is told they can resolve and run "git stash drop", + or run "git reset --hard" and later "git stash pop" to recover their + changes. Signed-off-by: Harald Nordgren @@ Documentation/git-checkout.adoc: $ git checkout mytopic ------------ $ git checkout -m mytopic -Auto-merging frotz -+Created autostash: 7a9afa3 +Applied autostash. ------------ @@ Documentation/git-checkout.adoc: $ git checkout mytopic -When a merge conflict happens during switching branches with -the `-m` option, you would see something like this: -+When the locally modified files overlap with files that need to be -+updated by the branch switch, the changes are stashed and reapplied -+after the switch. If the stash application results in conflicts, -+they are not resolved and the stash is saved to the stash list: ++When the `--merge` (`-m`) option is in effect and the locally ++modified files overlap with files that need to be updated by the ++branch switch, the changes are stashed and reapplied after the ++switch. If the stash application results in conflicts, they are not ++resolved and the stash is saved to the stash list: ------------ $ git checkout -m mytopic -Auto-merging frotz -ERROR: Merge conflict in frotz -fatal: merge program failed -+Created autostash: 7a9afa3 -+Applying autostash resulted in conflicts. -+Your changes are safe in the stash. -+You can run "git stash pop" or "git stash drop" at any time. - ------------ +------------- ++Your local changes are stashed, however, applying it to carry ++forward your local changes resulted in conflicts: -At this point, `git diff` shows the changes cleanly merged as in -the previous example, as well as the changes in the conflicted -files. Edit and resolve the conflict and mark it resolved with -`git add` as usual: -- -------------- ++ - You can try resolving them now. If you resolved them ++ successfully, discard the stash entry with "git stash drop". + ++ - Alternatively you can "git reset --hard" if you do not want ++ to deal with them right now, and later "git stash pop" to ++ recover your local changes. + ------------ -$ edit frotz -$ git add frotz ------------- -+At this point, `git stash pop` can be used to recover and resolve -+the conflicts, and `git stash drop` to discard the stash when done. ++ ++You can try resolving the conflicts now. Edit the conflicting files ++and mark them resolved with `git add` as usual, then run `git stash ++drop` to discard the stash entry. Alternatively, you can clear the ++working tree with `git reset --hard` and recover your local changes ++later with `git stash pop`. CONFIGURATION ------------- @@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commi static int switch_branches(const struct checkout_opts *opts, struct branch_info *new_branch_info) { +@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts, + struct object_id rev; + int flag, writeout_error = 0; + int do_merge = 1; ++ struct strbuf old_commit_shortname = STRBUF_INIT; ++ const char *stash_label_ancestor = NULL; + + trace2_cmd_mode("branch"); + @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts, do_merge = 0; } ++ if (old_branch_info.name) ++ stash_label_ancestor = old_branch_info.name; ++ else if (old_branch_info.commit) { ++ strbuf_add_unique_abbrev(&old_commit_shortname, ++ &old_branch_info.commit->object.oid, ++ DEFAULT_ABBREV); ++ stash_label_ancestor = old_commit_shortname.buf; ++ } ++ + if (opts->merge) { + if (repo_read_index(the_repository) < 0) + die(_("index file corrupt")); + if (checkout_would_clobber_changes(&old_branch_info, + new_branch_info)) -+ create_autostash_ref(the_repository, -+ "CHECKOUT_AUTOSTASH"); ++ create_autostash_ref_silent(the_repository, ++ "CHECKOUT_AUTOSTASH"); + } + if (do_merge) { ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); if (ret) { -+ apply_autostash_ref(the_repository, "CHECKOUT_AUTOSTASH"); ++ apply_autostash_ref_with_labels(the_repository, ++ "CHECKOUT_AUTOSTASH", ++ new_branch_info->name, ++ "local", ++ stash_label_ancestor); branch_info_release(&old_branch_info); ++ strbuf_release(&old_commit_shortname); return ret; } + } @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts, update_refs_for_switch(opts, &old_branch_info, new_branch_info); @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts, + git_config_push_parameter(cfg.buf); + strbuf_release(&cfg); + } -+ apply_autostash_ref(the_repository, "CHECKOUT_AUTOSTASH"); ++ apply_autostash_ref_with_labels(the_repository, "CHECKOUT_AUTOSTASH", ++ new_branch_info->name, "local", ++ stash_label_ancestor); + + discard_index(the_repository->index); + if (repo_read_index(the_repository) < 0) @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts, + ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1); branch_info_release(&old_branch_info); ++ strbuf_release(&old_commit_shortname); + + return ret || writeout_error; + } + + ## builtin/stash.c ## +@@ builtin/stash.c: static void unstage_changes_unless_new(struct object_id *orig_tree) + die(_("could not write index")); + } + +-static int do_apply_stash(const char *prefix, struct stash_info *info, +- int index, int quiet) ++static int do_apply_stash_with_labels(const char *prefix, ++ struct stash_info *info, ++ int index, int quiet, ++ const char *label1, const char *label2, ++ const char *label_ancestor) + { + int clean, ret; + int has_index = index; +@@ builtin/stash.c: static int do_apply_stash(const char *prefix, struct stash_info *info, + init_ui_merge_options(&o, the_repository); + +- o.branch1 = "Updated upstream"; +- o.branch2 = "Stashed changes"; +- o.ancestor = "Stash base"; ++ o.branch1 = label1 ? label1 : "Updated upstream"; ++ o.branch2 = label2 ? label2 : "Stashed changes"; ++ o.ancestor = label_ancestor ? label_ancestor : "Stash base"; + + if (oideq(&info->b_tree, &c_tree)) + o.branch1 = "Version stash was based on"; +@@ builtin/stash.c: restore_untracked: + return ret; + } + ++static int do_apply_stash(const char *prefix, struct stash_info *info, ++ int index, int quiet) ++{ ++ return do_apply_stash_with_labels(prefix, info, index, quiet, ++ NULL, NULL, NULL); ++} ++ + static int apply_stash(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) + { + int ret = -1; + int quiet = 0; + int index = use_index; ++ const char *label1 = NULL, *label2 = NULL, *label_ancestor = NULL; + struct stash_info info = STASH_INFO_INIT; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_BOOL(0, "index", &index, + N_("attempt to recreate the index")), ++ OPT_STRING(0, "ours-label", &label1, N_("label"), ++ N_("label for the upstream side in conflict markers")), ++ OPT_STRING(0, "theirs-label", &label2, N_("label"), ++ N_("label for the stashed side in conflict markers")), ++ OPT_STRING(0, "base-label", &label_ancestor, N_("label"), ++ N_("label for the base in diff3 conflict markers")), + OPT_END() + }; + +@@ builtin/stash.c: static int apply_stash(int argc, const char **argv, const char *prefix, + if (get_stash_info(&info, argc, argv)) + goto cleanup; + +- ret = do_apply_stash(prefix, &info, index, quiet); ++ ret = do_apply_stash_with_labels(prefix, &info, index, quiet, ++ label1, label2, label_ancestor); + cleanup: + free_stash_info(&info); + return ret; ## sequencer.c ## +@@ sequencer.c: static enum todo_command peek_command(struct todo_list *todo_list, int offset) + + static void create_autostash_internal(struct repository *r, + const char *path, +- const char *refname) ++ const char *refname, ++ int silent) + { + struct strbuf buf = STRBUF_INIT; + struct lock_file lock_file = LOCK_INIT; @@ sequencer.c: static void create_autostash_internal(struct repository *r, &oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR); } - printf(_("Created autostash: %s\n"), buf.buf); -+ fprintf(stderr, _("Created autostash: %s\n"), buf.buf); ++ if (!silent) ++ fprintf(stderr, _("Created autostash: %s\n"), buf.buf); if (reset_head(r, &ropts) < 0) die(_("could not reset --hard")); discard_index(r->index); +@@ sequencer.c: static void create_autostash_internal(struct repository *r, + + void create_autostash(struct repository *r, const char *path) + { +- create_autostash_internal(r, path, NULL); ++ create_autostash_internal(r, path, NULL, 0); + } + + void create_autostash_ref(struct repository *r, const char *refname) + { +- create_autostash_internal(r, NULL, refname); ++ create_autostash_internal(r, NULL, refname, 0); + } + +-static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) ++void create_autostash_ref_silent(struct repository *r, const char *refname) ++{ ++ create_autostash_internal(r, NULL, refname, 1); ++} ++ ++static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply, ++ const char *label1, const char *label2, ++ const char *label_ancestor) + { + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) + child.no_stderr = 1; + strvec_push(&child.args, "stash"); + strvec_push(&child.args, "apply"); ++ if (label1) ++ strvec_pushf(&child.args, "--ours-label=%s", label1); ++ if (label2) ++ strvec_pushf(&child.args, "--theirs-label=%s", label2); ++ if (label_ancestor) ++ strvec_pushf(&child.args, "--base-label=%s", label_ancestor); + strvec_push(&child.args, stash_oid); + ret = run_command(&child); + } +@@ sequencer.c: static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply) + strvec_push(&store.args, stash_oid); + if (run_command(&store)) + ret = error(_("cannot store %s"), stash_oid); ++ else if (attempt_apply) ++ fprintf(stderr, ++ _("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")); + else + fprintf(stderr, +- _("%s\n" ++ _("Autostash exists; creating a new stash entry.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" +- " \"git stash drop\" at any time.\n"), +- attempt_apply ? +- _("Applying autostash resulted in conflicts.") : +- _("Autostash exists; creating a new stash entry.")); ++ " \"git stash drop\" at any time.\n")); + } + + return ret; +@@ sequencer.c: static int apply_save_autostash(const char *path, int attempt_apply) + } + strbuf_trim(&stash_oid); + +- ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply); ++ ret = apply_save_autostash_oid(stash_oid.buf, attempt_apply, ++ NULL, NULL, NULL); + + unlink(path); + strbuf_release(&stash_oid); +@@ sequencer.c: int apply_autostash(const char *path) + + int apply_autostash_oid(const char *stash_oid) + { +- return apply_save_autostash_oid(stash_oid, 1); ++ return apply_save_autostash_oid(stash_oid, 1, NULL, NULL, NULL); + } + + static int apply_save_autostash_ref(struct repository *r, const char *refname, +- int attempt_apply) ++ int attempt_apply, ++ const char *label1, const char *label2, ++ const char *label_ancestor) + { + struct object_id stash_oid; + char stash_oid_hex[GIT_MAX_HEXSZ + 1]; +@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname, + return error(_("autostash reference is a symref")); + + oid_to_hex_r(stash_oid_hex, &stash_oid); +- ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply); ++ ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply, ++ label1, label2, label_ancestor); + + refs_delete_ref(get_main_ref_store(r), "", refname, + &stash_oid, REF_NO_DEREF); +@@ sequencer.c: static int apply_save_autostash_ref(struct repository *r, const char *refname, + + int save_autostash_ref(struct repository *r, const char *refname) + { +- return apply_save_autostash_ref(r, refname, 0); ++ return apply_save_autostash_ref(r, refname, 0, NULL, NULL, NULL); + } + + int apply_autostash_ref(struct repository *r, const char *refname) + { +- return apply_save_autostash_ref(r, refname, 1); ++ return apply_save_autostash_ref(r, refname, 1, NULL, NULL, NULL); ++} ++ ++int apply_autostash_ref_with_labels(struct repository *r, const char *refname, ++ const char *label1, const char *label2, ++ const char *label_ancestor) ++{ ++ return apply_save_autostash_ref(r, refname, 1, ++ label1, label2, label_ancestor); + } + + static int checkout_onto(struct repository *r, struct replay_opts *opts, - ## t/t7201-co.sh ## -@@ t/t7201-co.sh: test_expect_success 'format of merge conflict from checkout -m' ' - test_cmp expect current && - - cat <<-EOF >expect && -- <<<<<<< simple -+ <<<<<<< Updated upstream - a - c - e - ======= - b - d -- >>>>>>> local -+ >>>>>>> Stashed changes + ## sequencer.h ## +@@ sequencer.h: void commit_post_rewrite(struct repository *r, + + void create_autostash(struct repository *r, const char *path); + void create_autostash_ref(struct repository *r, const char *refname); ++void create_autostash_ref_silent(struct repository *r, const char *refname); + int save_autostash(const char *path); + int save_autostash_ref(struct repository *r, const char *refname); + int apply_autostash(const char *path); + int apply_autostash_oid(const char *stash_oid); + int apply_autostash_ref(struct repository *r, const char *refname); ++int apply_autostash_ref_with_labels(struct repository *r, const char *refname, ++ const char *label1, const char *label2, ++ const char *label_ancestor); + + #define SUMMARY_INITIAL_COMMIT (1 << 0) + #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1) + + ## t/t3420-rebase-autostash.sh ## +@@ t/t3420-rebase-autostash.sh: create_expected_failure_apply () { + First, rewinding head to replay your work on top of it... + Applying: second commit + Applying: third commit +- Applying autostash resulted in conflicts. +- Your changes are safe in the stash. +- You can run "git stash pop" or "git stash drop" at any time. ++ Your local changes are stashed, however, applying it to carry ++ forward your local changes resulted in conflicts: ++ ++ - You can try resolving them now. If you resolved them ++ successfully, discard the stash entry with "git stash drop". ++ ++ - Alternatively you can "git reset --hard" if you do not want ++ to deal with them right now, and later "git stash pop" to ++ recover your local changes. EOF - test_cmp expect two - ' -@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' ' - git checkout --merge --conflict=diff3 simple && - - cat <<-EOF >expect && -- <<<<<<< simple -+ <<<<<<< Updated upstream - a - c - e -- ||||||| main -+ ||||||| Stash base - a - b - c -@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' ' - ======= - b - d -- >>>>>>> local -+ >>>>>>> Stashed changes + } + + create_expected_failure_merge () { + cat >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) +- Applying autostash resulted in conflicts. +- Your changes are safe in the stash. +- You can run "git stash pop" or "git stash drop" at any time. ++ Your local changes are stashed, however, applying it to carry ++ forward your local changes resulted in conflicts: ++ ++ - You can try resolving them now. If you resolved them ++ successfully, discard the stash entry with "git stash drop". ++ ++ - Alternatively you can "git reset --hard" if you do not want ++ to deal with them right now, and later "git stash pop" to ++ recover your local changes. + Successfully rebased and updated refs/heads/rebased-feature-branch. EOF + } + + ## t/t7201-co.sh ## +@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' ' test_cmp expect two ' @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + + cat <<-EOF >expect && + a -+ <<<<<<< Updated upstream ++ <<<<<<< simple + c -+ ||||||| Stash base ++ ||||||| main + b + c + d @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + b + X + d -+ >>>>>>> Stashed changes ++ >>>>>>> local + e + EOF + test_cmp expect two @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + git checkout -m simple && + + cat <<-EOF >expect && -+ <<<<<<< Updated upstream ++ <<<<<<< simple + a + c + e -+ ||||||| Stash base ++ ||||||| main + a + b + c @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + ======= + b + d -+ >>>>>>> Stashed changes ++ >>>>>>> local + EOF + test_cmp expect two +' @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + + fill 1 2 3 4 5 6 7 >one && + git checkout -m side >actual 2>&1 && -+ test_grep "Created autostash" actual && ++ test_grep ! "Created autostash" actual && + test_grep "Applied autostash" actual && + fill 1 2 3 4 5 6 7 >expect && + test_cmp expect one @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + git add same && + fill 1 2 3 4 5 6 7 >one && + git checkout -m side >actual 2>&1 && -+ test_grep "Created autostash" actual && ++ test_grep ! "Created autostash" actual && + test_grep "Applied autostash" actual && + fill 0 x y z >expect && + test_cmp expect same && @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + test_must_fail git checkout side 2>stderr && + test_grep "Your local changes" stderr && + git checkout -m side >actual 2>&1 && -+ test_grep "Created autostash" actual && -+ test_grep "Applying autostash resulted in conflicts" actual && -+ test_grep "Your changes are safe in the stash" actual && ++ test_grep ! "Created autostash" actual && ++ test_grep "resulted in conflicts" actual && ++ test_grep "git stash drop" actual && + git stash drop && + git reset --hard +' @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + + fill 1 2 3 4 5 >one && + git checkout -m side >actual 2>&1 && -+ test_grep "Your changes are safe in the stash" actual && ++ test_grep "recover your local changes" actual && + git checkout -f main && + git stash pop && + fill 1 2 3 4 5 >expect && @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' + fill 1 2 3 4 5 >one && + git add one && + git checkout -m side >actual 2>&1 && -+ test_grep "Created autostash" actual && -+ test_grep "Applying autostash resulted in conflicts" actual && -+ test_grep "Your changes are safe in the stash" actual && ++ test_grep ! "Created autostash" actual && ++ test_grep "resulted in conflicts" actual && ++ test_grep "git stash drop" actual && + git stash drop && + git reset --hard +' @@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 ' git checkout -f main && git reset --hard && + ## t/t7600-merge.sh ## +@@ t/t7600-merge.sh: test_expect_success 'merge --squash --autostash conflict does not attempt to app + >unrelated && + git add unrelated && + test_must_fail git merge --squash c7 --autostash >out 2>err && +- ! grep "Applying autostash resulted in conflicts." err && ++ ! grep "resulted in conflicts" err && + grep "When finished, apply stashed changes with \`git stash pop\`" out + ' + +@@ t/t7600-merge.sh: test_expect_success 'merge with conflicted --autostash changes' ' + git diff >expect && + test_when_finished "test_might_fail git stash drop" && + git merge --autostash c3 2>err && +- test_grep "Applying autostash resulted in conflicts." err && ++ test_grep "resulted in conflicts" err && + git show HEAD:file >merge-result && + test_cmp result.1-9 merge-result && + git stash show -p >actual && + ## xdiff-interface.c ## @@ xdiff-interface.c: int parse_conflict_style_name(const char *value) return -1; Documentation/git-checkout.adoc | 56 +++++----- Documentation/git-switch.adoc | 26 ++--- builtin/checkout.c | 179 +++++++++++++++++--------------- builtin/stash.c | 30 ++++-- sequencer.c | 67 +++++++++--- sequencer.h | 4 + t/t3420-rebase-autostash.sh | 24 +++-- t/t7201-co.sh | 160 ++++++++++++++++++++++++++++ t/t7600-merge.sh | 4 +- xdiff-interface.c | 12 +++ xdiff-interface.h | 1 + 11 files changed, 412 insertions(+), 151 deletions(-) diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc index 43ccf47cf6..9d5f5c51ae 100644 --- a/Documentation/git-checkout.adoc +++ b/Documentation/git-checkout.adoc @@ -251,20 +251,17 @@ working tree, by copying them from elsewhere, extracting a tarball, etc. are different between the current branch and the branch to which you are switching, the command refuses to switch branches in order to preserve your modifications in context. - However, with this option, a three-way merge between the current - branch, your working tree contents, and the new branch - is done, and you will be on the new branch. -+ -When a merge conflict happens, the index entries for conflicting -paths are left unmerged, and you need to resolve the conflicts -and mark the resolved paths with `git add` (or `git rm` if the merge -should result in deletion of the path). + With this option, the conflicting local changes are + automatically stashed before the switch and reapplied + afterwards. If the local changes do not overlap with the + differences between branches, the switch proceeds without + stashing. If reapplying the stash results in conflicts, the + entry is saved to the stash list so you can use `git stash + pop` to recover and `git stash drop` when done. + When checking out paths from the index, this option lets you recreate the conflicted merge in the specified paths. This option cannot be used when checking out paths from a tree-ish. -+ -When switching branches with `--merge`, staged changes may be lost. `--conflict=