From: Harald Nordgren via GitGitGadget Date: Tue, 17 Mar 2026 09:35:35 GMT Subject: [PATCH v6 0/4] checkout: 'autostash' for branch switching Message-ID: In-Reply-To: cc: Phillip Wood phillip.wood123@gmail.com Harald Nordgren (4): stash: add --ours-label, --theirs-label, --base-label for apply sequencer: allow create_autostash to run silently sequencer: teach autostash apply to take optional conflict marker labels checkout: -m (--merge) uses autostash when switching branches Documentation/git-checkout.adoc | 58 ++++++------ Documentation/git-stash.adoc | 11 ++- Documentation/git-switch.adoc | 27 +++--- builtin/checkout.c | 137 ++++++++++++--------------- builtin/stash.c | 32 +++++-- sequencer.c | 67 +++++++++---- sequencer.h | 4 + t/t3420-rebase-autostash.sh | 24 +++-- t/t3903-stash.sh | 18 ++++ t/t7201-co.sh | 160 ++++++++++++++++++++++++++++++++ t/t7600-merge.sh | 2 +- xdiff-interface.c | 12 +++ xdiff-interface.h | 1 + 13 files changed, 403 insertions(+), 150 deletions(-) base-commit: ca1db8a0f7dc0dbea892e99f5b37c5fe5861be71 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v6 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v6 Pull-Request: https://github.com/git/git/pull/2234 Range-diff vs v5: 1: 00d8920498 = 1: cd9c64ba60 stash: add --ours-label, --theirs-label, --base-label for apply 2: 5d176f1700 = 2: d572c4bb7d sequencer: allow create_autostash to run silently 3: 3d6829438a = 3: 80a98116fc sequencer: teach autostash apply to take optional conflict marker labels 4: 7f3735d40e ! 4: 7ecb0835b7 checkout: -m (--merge) uses autostash when switching branches @@ builtin/checkout.c #include "submodule.h" #include "symlinks.h" @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts, + struct tree *new_tree; + repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); +- if (repo_read_index_preload(the_repository, NULL, 0) < 0) ++ if (repo_read_index_preload(the_repository, NULL, 0) < 0) { ++ rollback_lock_file(&lock_file); + return error(_("index file corrupt")); ++ } + + resolve_undo_clear_index(the_repository->index); + if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts, + } else { + new_tree = repo_get_commit_tree(the_repository, + new_branch_info->commit); +- if (!new_tree) ++ if (!new_tree) { ++ rollback_lock_file(&lock_file); + return error(_("unable to read tree (%s)"), + oid_to_hex(&new_branch_info->commit->object.oid)); ++ } + } + if (opts->discard_changes) { + ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); +- if (ret) ++ if (ret) { ++ rollback_lock_file(&lock_file); + return ret; ++ } + } else { + struct tree_desc trees[2]; + struct tree *tree; +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts, + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); + + if (unmerged_index(the_repository->index)) { ++ rollback_lock_file(&lock_file); + error(_("you need to resolve your current index first")); + return 1; + } +@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts, ret = unpack_trees(2, trees, &topts); clear_unpack_trees_porcelain(&topts); -- if (ret == -1) { + if (ret == -1) { - /* - * Unpack couldn't do a trivial merge; either - * give up or do a real merge, depending on @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op - strbuf_release(&old_commit_shortname); - if (ret) - return ret; -- } -+ if (ret == -1) ++ rollback_lock_file(&lock_file); + return 1; + } } - if (!cache_tree_fully_valid(the_repository->index->cache_tree)) -@@ builtin/checkout.c: static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne - release_revisions(&revs); - } - -+static int checkout_would_clobber_changes(struct branch_info *old_branch_info, -+ struct branch_info *new_branch_info) -+{ -+ struct tree_desc trees[2]; -+ struct tree *old_tree, *new_tree; -+ struct unpack_trees_options topts; -+ struct index_state tmp_index = INDEX_STATE_INIT(the_repository); -+ const struct object_id *old_commit_oid; -+ int ret; -+ -+ if (!new_branch_info->commit) -+ return 0; -+ -+ old_commit_oid = old_branch_info->commit ? -+ &old_branch_info->commit->object.oid : -+ the_hash_algo->empty_tree; -+ old_tree = repo_parse_tree_indirect(the_repository, old_commit_oid); -+ if (!old_tree) -+ return 0; -+ -+ new_tree = repo_get_commit_tree(the_repository, -+ new_branch_info->commit); -+ if (!new_tree) -+ return 0; -+ if (repo_parse_tree(the_repository, new_tree) < 0) -+ return 0; -+ -+ memset(&topts, 0, sizeof(topts)); -+ topts.head_idx = -1; -+ topts.src_index = the_repository->index; -+ topts.dst_index = &tmp_index; -+ topts.initial_checkout = is_index_unborn(the_repository->index); -+ topts.merge = 1; -+ topts.update = 1; -+ topts.dry_run = 1; -+ topts.quiet = 1; -+ topts.fn = twoway_merge; -+ -+ init_tree_desc(&trees[0], &old_tree->object.oid, -+ old_tree->buffer, old_tree->size); -+ init_tree_desc(&trees[1], &new_tree->object.oid, -+ new_tree->buffer, new_tree->size); -+ -+ ret = unpack_trees(2, trees, &topts); -+ discard_index(&tmp_index); -+ -+ return ret != 0; -+} -+ - 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; @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts, + 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)) { + if (do_merge) { + ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); ++ if (ret && opts->merge) { + create_autostash_ref_silent(the_repository, + "CHECKOUT_AUTOSTASH"); + created_autostash = 1; ++ ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); + } -+ } -+ - if (do_merge) { - ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); if (ret) { + apply_autostash_ref_with_labels(the_repository, + "CHECKOUT_AUTOSTASH", -- gitgitgadget