Git development
 help / color / mirror / Atom feed
From: Phillip Wood <phillip.wood123@gmail.com>
To: "D. Ben Knoble" <ben.knoble@gmail.com>, git@vger.kernel.org
Cc: "Eli Barzilay" <eli@barzilay.org>,
	"Phillip Wood" <phillip.wood@dunelm.org.uk>,
	"Taylor Blau" <me@ttaylorr.com>, "Patrick Steinhardt" <ps@pks.im>,
	"Derrick Stolee" <stolee@gmail.com>, "Adam Johnson" <me@adamj.eu>,
	"Junio C Hamano" <gitster@pobox.com>, "Jeff King" <peff@peff.net>,
	"Johannes Schindelin" <Johannes.Schindelin@gmx.de>,
	"Victoria Dye" <vdye@github.com>,
	"Elijah Newren" <newren@gmail.com>,
	"Ævar Arnfjörð Bjarmason" <avarab@gmail.com>
Subject: Re: [PATCH 2/2] builtin/stash: merge index in-core
Date: Mon, 21 Sep 2026 14:17:31 +0100	[thread overview]
Message-ID: <2551b801-4cb3-4880-ac01-7d14a188ddd4@gmail.com> (raw)
In-Reply-To: <782fe91251111fbb28359574d860e4a6d2e45fc0.1789853192.git.ben.knoble@gmail.com>

Hi Ben

On 19/09/2026 22:26, D. Ben Knoble wrote:
> "git stash apply --index" does a 2-step dance to report index conflicts
> before carrying out the main unstash: first, attempt to merge the index
> (and remember the name of the resulting tree). If that succeeds, reset
> the index and carry on unstashing the working tree, then use the
> remembered index tree to unstash the index.
> 
> The "merge the index" step is performed on the actual index by a
> combination of git-diff-tree(1) and git-apply(1), which incurs an extra
> cost to git-reset(1) to cleanup. This also introduces an autostash bug
> when stash.index is true: "git reset" eventually wants to
> remove_merge_branch_state(), which calls save_autostash() due to
> a03b55530a (merge: teach --autostash option, 2020-04-07). This can
> happen from a "git merge --autostash", which itself calls
> save_autostash(). Operating on the file-system in this way is not
> re-entrant, so we end up trying to lock a now-deleted MERGE_AUTOSTASH
> ref [1]. This bug has lurked for a while, but it would have been
> impossible to trigger without the availability of stash.index to force
> the autostash apply into index mode.
> 
> [1]: https://lore.kernel.org/git/CALO-guvbk2TcrVwzdNQ3yRpzHr0HHZ3h1wite0Xp0sUyAT4otA@mail.gmail.com/
> 
> Fortunately, we can achieve 2 goals at once: avoid round-tripping to the
> file-system (and invoking expensive subprocesses) by performing the
> merge in-core. Since the results are never seen, we don't need to set
> the usual branch and ancestor labels.

When the merge succeeds without conflicts we use the result so it is 
seen. It would be clearer to say that "If there are conflicts we discard 
the result so ...". The rest of the commit message explains the problem 
nicely.

> We *could* swap just the git-reset(1) subprocess with our internal
> reset_tree() and refresh_index(), which would fix the bug. We'd much
> prefer to clean up these vestiges of the shell-based git-stash, though.

Definitely

>   builtin/stash.c  | 76 +++++++++---------------------------------------

Nice diffstat!

> @@ -669,29 +625,25 @@ static enum stash_apply_result do_apply_stash(const char *prefix,
>   		    oideq(&c_tree, &info->i_tree)) {
>   			has_index = 0;
>   		} else {
> -			struct strbuf out = STRBUF_INIT;
> +			struct merge_result result = { 0 };
>   
> -			if (diff_tree_binary(&out, &info->w_commit)) {
> -				strbuf_release(&out);
> -				return error(_("could not generate diff %s^!."),
> -					     oid_to_hex(&info->w_commit));
> -			}
> +			init_basic_merge_options(&o, the_repository);

This means we potentially use different diff algorithms when merging the 
index and when merging the work tree, let's use the _ui variant here 
instead.

>   
> -			ret = apply_cached(&out);
> -			strbuf_release(&out);
> -			if (ret)
> +			o.verbosity = 0;

Looking at the code in merge-ort.c it appears the verbosity option was 
used by the recursive strategy but isn't used anymore so I think we 
could drop this.

> +
> +			head = lookup_tree(o.repo, &c_tree);
> +			merge = lookup_tree(o.repo, &info->i_tree);
> +			merge_base = lookup_tree(o.repo, &info->b_tree);
> +
> +			merge_incore_nonrecursive(&o, head, merge, merge_base,
> +						  &result);
> +
> +			if (!result.clean)
>   				return error(_("conflicts in index. "
>   					       "Try without --index."));
>   
> -			discard_index(the_repository->index);
> -			repo_read_index(the_repository);
> -			if (write_index_as_tree(&index_tree, the_repository->index,
> -						repo_get_index_file(the_repository), 0, NULL))
> -				return error(_("could not save index tree"));
> -
> -			reset_head();
> -			discard_index(the_repository->index);
> -			repo_read_index(the_repository);
> +			oidcpy(&index_tree, &result.tree->object.oid);
> +			clear_merge_options(&o);

Looking at replay.c:replay_revisions() I think this should be

merge_finalize(&opts, &result);

>   		}
>   	}
>   
> diff --git a/merge-ort.c b/merge-ort.c
> index c410a5d353..f69a49d48a 100644
> --- a/merge-ort.c
> +++ b/merge-ort.c
> @@ -5035,8 +5035,6 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
>   	trace2_region_enter("merge", "sanity checks", opt->repo);
>   	assert(opt->repo);
>   
> -	assert(opt->branch1 && opt->branch2);

This, and the hunk below, make me nervous. Normally assertions like this 
exist because the pointers are unconditionally dereferenced later on. 
Looking at merge_3way() it asserts opt->ancestor is non-NULL and 
dereferences all three labels. t3903 does not appear to have test 
coverage for the index merge failing (if it did I think we'd see a 
SIGSEV), we should probably add a test that checks the command fails 
leaving the index and work tree untouched, and verifies the message on 
stderr.

Lets set some simple, fixed, ancestor and branch names in 
do_apply_stash() above.

>   	assert(opt->detect_directory_renames >= MERGE_DIRECTORY_RENAMES_NONE &&
>   	       opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE);
>   	assert(opt->rename_limit >= -1);
> @@ -5409,7 +5407,6 @@ void merge_incore_nonrecursive(struct merge_options *opt,
>   	trace2_region_enter("merge", "incore_nonrecursive", opt->repo);
>   
>   	trace2_region_enter("merge", "merge_start", opt->repo);
> -	assert(opt->ancestor != NULL);
>   	merge_check_renames_reusable(opt, result, merge_base, side1, side2);
>   	merge_start(opt, result);
>   	/*

> +test_expect_success 'fast-forward merge with --autostash, stash.index' '
> +	git reset --hard c0 &&
> +	git stash clear &&
> +	echo staged >>z && git add z &&
> +	git -c stash.index=true merge --autostash c1 2>err &&
> +	test_grep "Applied autostash." err &&
> +	test_stdout_line_count = 0 git stash list
> +'

We check the autostash is applied and is not saved - good

Thanks for working on this, it is really good to get rid of those 
subprocesses.

Phillip

>   test_expect_success 'failed fast-forward merge with --autostash' '
>   	git reset --hard c0 &&
>   	git merge-file file file.orig file.5 &&


  reply	other threads:[~2026-09-21 13:17 UTC|newest]

Thread overview: 78+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-19 21:26 [PATCH 0/2] Hi all, D. Ben Knoble
2026-09-19 21:26 ` [PATCH 1/2] builtin/stash: remove unused header D. Ben Knoble
2026-09-21 15:10   ` Junio C Hamano
2026-09-19 21:26 ` [PATCH 2/2] builtin/stash: merge index in-core D. Ben Knoble
2026-09-21 13:17   ` Phillip Wood [this message]
2026-09-22 12:43     ` D. Ben Knoble
2026-09-22 12:51       ` D. Ben Knoble
2026-09-22 13:57       ` Phillip Wood
2026-09-22 20:34         ` D. Ben Knoble
2026-09-19 21:32 ` [PATCH 0/2] Hi all, D. Ben Knoble
2026-09-23 12:58 ` [PATCH v2 0/4] stash: clean up index-mode test merge D. Ben Knoble
2026-09-23 12:58   ` [PATCH v2 1/4] builtin/stash: remove unused header D. Ben Knoble
2026-09-23 12:58   ` [PATCH v2 2/4] stash: prepare merge options earlier D. Ben Knoble
2026-09-23 12:58   ` [PATCH v2 3/4] t: test failed "stash apply --index" D. Ben Knoble
2026-09-24  9:42     ` Phillip Wood
2026-09-25 13:36       ` D. Ben Knoble
2026-09-25 15:45         ` Phillip Wood
2026-09-26  9:53           ` Phillip Wood
2026-09-26 12:07             ` D. Ben Knoble
2026-09-23 12:58   ` [PATCH v2 4/4] builtin/stash: merge index in-core D. Ben Knoble
2026-09-24  9:42     ` Phillip Wood
2026-09-25 12:55       ` D. Ben Knoble
2026-09-25 15:58         ` Phillip Wood
2026-09-25 16:16           ` D. Ben Knoble
2026-09-24 21:59     ` Junio C Hamano
2026-09-25  4:12       ` Junio C Hamano
2026-09-25 13:00       ` D. Ben Knoble
2026-09-25 16:24         ` Junio C Hamano
2026-09-26  9:51           ` Phillip Wood
2026-09-26 12:04             ` D. Ben Knoble
2026-09-25 16:04       ` Phillip Wood
2026-09-25 16:17         ` D. Ben Knoble
2026-09-25 16:49           ` Junio C Hamano
2026-09-26 12:16   ` [PATCH v3 0/5] stash: clean up index-mode test merge D. Ben Knoble
2026-09-26 12:16     ` [PATCH v3 1/5] builtin/stash: remove unused header D. Ben Knoble
2026-09-26 12:16     ` [PATCH v3 2/5] stash: prepare merge options earlier D. Ben Knoble
2026-09-26 12:16     ` [PATCH v3 3/5] t3903: test stash --index merges D. Ben Knoble
2026-09-28 15:44       ` Phillip Wood
2026-09-28 15:55         ` D. Ben Knoble
2026-09-29  9:41           ` Phillip Wood
2026-09-26 12:16     ` [PATCH v3 4/5] t3903: test failed "stash apply --index" D. Ben Knoble
2026-09-26 12:16     ` [PATCH v3 5/5] builtin/stash: merge index in-core D. Ben Knoble
2026-09-27 18:59       ` Junio C Hamano
2026-09-28 12:02         ` D. Ben Knoble
2026-09-28  9:40       ` Junio C Hamano
2026-09-28 12:03         ` D. Ben Knoble
2026-09-28 15:32           ` Junio C Hamano
2026-09-26 12:20     ` [PATCH v3 0/5] stash: clean up index-mode test merge D. Ben Knoble
2026-09-27 19:21     ` Junio C Hamano
2026-09-28  9:50       ` Phillip Wood
2026-09-28 12:05         ` D. Ben Knoble
2026-09-28 12:33           ` D. Ben Knoble
2026-09-28 13:00             ` D. Ben Knoble
2026-09-28 13:45               ` Phillip Wood
2026-09-28 14:50                 ` Thomas Bachem
2026-09-28 15:36                   ` D. Ben Knoble
2026-09-29 11:38                     ` D. Ben Knoble
2026-09-29 15:54                     ` Phillip Wood
2026-09-28 15:40                   ` Phillip Wood
2026-09-29 12:18 ` [PATCH v4 " D. Ben Knoble
2026-09-29 12:18   ` [PATCH v4 1/5] builtin/stash: remove unused header D. Ben Knoble
2026-09-29 12:18   ` [PATCH v4 2/5] stash: prepare merge options earlier D. Ben Knoble
2026-09-29 12:18   ` [PATCH v4 3/5] t3903: test failed "stash apply --index" D. Ben Knoble
2026-09-29 12:18   ` [PATCH v4 4/5] t5520: don't expire reflogs where it matters D. Ben Knoble
2026-09-29 15:46     ` Phillip Wood
2026-09-29 12:18   ` [PATCH v4 5/5] builtin/stash: merge index in-core D. Ben Knoble
2026-09-29 20:07     ` Junio C Hamano
2026-09-30  1:29       ` D. Ben Knoble
2026-09-29 15:48   ` [PATCH v4 0/5] stash: clean up index-mode test merge Phillip Wood
2026-09-29 17:31     ` Ben Knoble
2026-09-30 21:26       ` D. Ben Knoble
2026-09-30 21:24 ` [PATCH v5 0/4] " D. Ben Knoble
2026-09-30 21:24   ` [PATCH v5 1/4] builtin/stash: remove unused header D. Ben Knoble
2026-09-30 21:24   ` [PATCH v5 2/4] stash: prepare merge options earlier D. Ben Knoble
2026-09-30 21:24   ` [PATCH v5 3/4] t3903: test failed "stash apply --index" D. Ben Knoble
2026-09-30 21:24   ` [PATCH v5 4/4] builtin/stash: merge index in-core D. Ben Knoble
2026-10-01 15:52   ` [PATCH v5 0/4] stash: clean up index-mode test merge Phillip Wood
2026-10-01 17:47     ` Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=2551b801-4cb3-4880-ac01-7d14a188ddd4@gmail.com \
    --to=phillip.wood123@gmail.com \
    --cc=Johannes.Schindelin@gmx.de \
    --cc=avarab@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=eli@barzilay.org \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=me@adamj.eu \
    --cc=me@ttaylorr.com \
    --cc=newren@gmail.com \
    --cc=peff@peff.net \
    --cc=phillip.wood@dunelm.org.uk \
    --cc=ps@pks.im \
    --cc=stolee@gmail.com \
    --cc=vdye@github.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox