git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 03/19] Ensure index matches head before invoking merge machinery, round N

From
Johannes Schindelin <johannes.schindelin@gmx.de>
Date
Jul 25, 2019, 19:41 UTC
Message-ID
<nycvar.QRO.7.76.6.1907252120300.21907@tvgsbejvaqbjf.bet>
In-Reply-To
<20190725174611.14802-4-newren@gmail.com>
Hi Elijah,
On Thu, 25 Jul 2019, Elijah Newren wrote:
Show 46 quoted lines
> This is the bug that just won't die; there always seems to be another
> form of it somewhere.  See the commit message of 55f39cf7551b ("merge:
> fix misleading pre-merge check documentation", 2018-06-30) for a more
> detailed explanation), but in short:
>
> <quick summary>
>
> builtin/merge.c contains this important requirement for merge
> strategies:
>
>     ...the index must be in sync with the head commit.  The strategies are
>     responsible to ensure this.
>
> This condition is important to enforce because there are two likely
> failure cases when the index isn't in sync with the head commit:
>
>   * we silently throw away changes the user had staged before the merge
>
>   * we accidentally (and silently) include changes in the merge that
>     were not part of either of the branches/trees being merged
>
> Discarding users' work and mis-merging are both bad outcomes, especially
> when done silently, so naturally this rule was stated sternly -- but,
> unfortunately totally ignored in practice unless and until actual bugs
> were found.  But, fear not: the bugs from this were fixed in commit
>   ee6566e8d70d ("[PATCH] Rewrite read-tree", 2005-09-05)
> through a rewrite of read-tree (again, commit 55f39cf7551b has a more
> detailed explanation of how this affected merge).  And it was fixed
> again in commit
>   160252f81626 ("git-merge-ours: make sure our index matches HEAD", 2005-11-03)
> ...and it was fixed again in commit
>   3ec62ad9ffba ("merge-octopus: abort if index does not match HEAD", 2016-04-09)
> ...and again in commit
>   65170c07d466 ("merge-recursive: avoid incorporating uncommitted changes in a merge", 2017-12-21)
> ...and again in commit
>   eddd1a411d93 ("merge-recursive: enforce rule that index matches head before merging", 2018-06-30)
>
> ...with multiple testcases added to the testsuite that could be
> enumerated in even more commits.
>
> Then, finally, in a patch in the same series as the last fix above, the
> documentation about this requirement was fixed in commit 55f39cf7551b
> ("merge: fix misleading pre-merge check documentation", 2018-06-30), and
> we all lived happily ever after...
>
> </quick summary>
Whoa. What a story.
Show 27 quoted lines
> Unfortunately, "ever after" apparently denotes a limited time and it
> expired today.  The merge-recursive rule to enforce that index matches
> head was at the beginning of merge_trees() and would only trigger when
> opt->call_depth was 0.  Since merge_recursive() doesn't call
> merge_trees() until after returning from recursing, this meant that the
> check wasn't triggered by merge_recursive() until it had first finished
> all the intermediate merges to create virtual merge bases.  That is a
> potentially HUGE amount of computation (and writing of intermediate
> merge results into the .git/objects directory) before it errors out and
> says, in effect, "Sorry, I can't do any merging because you have some
> local changes that would be overwritten."
>
> Trying to enforce that all of merge_trees(), merge_recursive(), and
> merge_recursive_generic() checked the index == head condition earlier
> resulted in a bunch of broken tests.  It turns out that
> merge_recursive() has code to drop and reload the cache while recursing
> to create intermediate virtual merge bases, but unfortunately that code
> runs even when no recursion is necessary.  This unconditional dropping
> and reloading of the cache masked a few bugs:
>
>   * builtin/merge-recursive.c: didn't even bother loading the index.
>
>   * builtin/stash.c: feels like a fake 'builtin' because it repeatedly
>     invokes git subprocesses all over the place, mixed with other
>     operations.  In particular, invoking "git reset" will reset the
>     index on disk, but the parent process that invoked it won't
>     automatically have its in-memory index updated.

Yep, the idea was to move fast to a built-in, and then continue by converting all those process spawns to proper calls to libgit "API" functions.

Sadly, that did not happen yet.

And you're absolutely right that failing to re-read the index after spawning a `reset --hard` causes problems. IIRC I fixed them all, though, see https://public-inbox.org/git/nycvar.QRO.7.76.6.1902191127420.41@tvgsbejvaqbjf.bet/

> So, load the index in builtin/merge-recursive.c, reload the in-memory
> index in builtin/stash.c, and modify the t3030 testcase to correctly
> setup the index and make sure that the test fails in the expected way
> (meaning it reports a rename/rename conflict).
Makes sense to me.
Show 15 quoted lines
> diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c
> index 5b910e351e..a4bfd8fc51 100644
> --- a/builtin/merge-recursive.c
> +++ b/builtin/merge-recursive.c
> @@ -1,3 +1,4 @@
> +#include "cache.h"
>  #include "builtin.h"
>  #include "commit.h"
>  #include "tag.h"
> @@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
>  	if (argc - i != 3) /* "--" "<head>" "<remote>" */
>  		die(_("not handling anything other than two heads merge."));
>
> +	if (repo_read_index_unmerged(the_repository))
> +		die_resolve_conflict("merge");

For a moment I was unsure whether `_unmerged()` is the right thing to do here, as it specifically allows to read the index even when there are conflict stages. But I guess it does not matter too much here. I probably would have opted for `repo_read_index()` instead, though.

Show 14 quoted lines
> +
>  	o.branch1 = argv[++i];
>  	o.branch2 = argv[++i];
>
> diff --git a/builtin/stash.c b/builtin/stash.c
> index fde6397caa..bec011c1bb 100644
> --- a/builtin/stash.c
> +++ b/builtin/stash.c
> @@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
>  				return error(_("could not save index tree"));
>
>  			reset_head();
> +			discard_cache();
> +			read_cache();

I was honestly puzzled why this is necessary, at first. The preceding context expands to this:

                        discard_cache();
                        read_cache();
                        if (write_cache_as_tree(&index_tree, 0, NULL))
                                return error(_("could not save index tree"));

So basically, we already discard the index, read it again, then write it as a tree, then reset and then we have to discard the index again?

But of course, if there are uncommitted changes, this would write a tree different from HEAD, then reset the index to match HEAD, so indeed, this discard/read dance is necessary.

So this hunk is good.
Show 28 quoted lines
>  		}
>  	}
>
> diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
> index ff641b348a..a37bcc58a0 100755
> --- a/t/t3030-merge-recursive.sh
> +++ b/t/t3030-merge-recursive.sh
> @@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '
>  test_expect_success 'merge-recursive remembers the names of all base trees' '
>  	git reset --hard HEAD &&
>
> +	# make the index match $c1 so that merge-recursive below does not
> +	# fail early
> +	git diff --binary HEAD $c1 -- | git apply --cached &&
> +
>  	# more trees than static slots used by oid_to_hex()
>  	for commit in $c0 $c2 $c4 $c5 $c6 $c7
>  	do
>  		git rev-parse "$commit^{tree}"
>  	done >trees &&
>
> -	# ignore the return code -- it only fails because the input is weird
> +	# ignore the return code; it only fails because the input is weird...
>  	test_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&
>
> +	# ...but make sure it fails in the expected way
> +	test_i18ngrep CONFLICT.*rename/rename out &&
> +

This is obviously a good change: it strengthens the test case by fixing a subtle bug.

Thanks, Dscho

Show 7 quoted lines
>  	# merge-recursive prints in reverse order, but we do not care
>  	sort <trees >expect &&
>  	sed -n "s/^virtual //p" out | sort >actual &&
> --
> 2.22.0.559.g28a8880890.dirty
>
>
Previous: Elijah NewrenNext: Elijah Newren
Message 9 of 157 in “Cleanup merge API”
  1. 00/19 Cleanup merge APIElijah Newren, Jul 25, 2019
  2. 01/19 merge-recursive: fix minor memory leak in error conditionElijah Newren, Jul 25, 2019
  3. 02/19 merge-recursive: remove another implicit dependency on the_repositoryElijah Newren, Jul 25, 2019
  4. 05/19 merge-recursive: don't force external callers to do our loggingElijah Newren, Jul 25, 2019
  5. 04/19 merge-recursive: exit early if index != headElijah Newren, Jul 25, 2019
  6. Johannes SchindelinJul 25, 2019
  7. Elijah NewrenJul 25, 2019
  8. 03/19 Ensure index matches head before invoking merge machinery, round NElijah Newren, Jul 25, 2019
  9. Johannes SchindelinJul 25, 2019
  10. Elijah NewrenJul 25, 2019
  11. Johannes SchindelinJul 26, 2019
  12. 07/19 Move write_tree_from_memory() from merge-recursive to cache-treeElijah Newren, Jul 25, 2019
  13. 08/19 merge-recursive: fix some overly long linesElijah Newren, Jul 25, 2019
  14. 09/19 merge-recursive: use common name for ancestors/common/base_listElijah Newren, Jul 25, 2019
  15. 10/19 merge-recursive: rename 'mrtree' to 'result_tree', for clarityElijah Newren, Jul 25, 2019
  16. Johannes SchindelinJul 25, 2019
  17. 11/19 merge-recursive: rename merge_options argument to opt in headerElijah Newren, Jul 25, 2019
  18. 13/19 merge-recursive: consolidate unnecessary fields in merge_optionsElijah Newren, Jul 25, 2019
  19. 12/19 merge-recursive: move some definitions around to clean up the headerElijah Newren, Jul 25, 2019
  20. 15/19 merge-recursive: split internal fields into a separate structElijah Newren, Jul 25, 2019
  21. Johannes SchindelinJul 25, 2019
  22. Elijah NewrenJul 25, 2019
  23. Johannes SchindelinJul 26, 2019
  24. Elijah NewrenJul 26, 2019
  25. 17/19 merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*Elijah Newren, Jul 25, 2019
  26. 16/19 merge-recursive: alphabetize include listElijah Newren, Jul 25, 2019
  27. 18/19 merge-recursive: be consistent with assertElijah Newren, Jul 25, 2019
  28. 19/19 merge-recursive: provide a better label for diff3 common ancestorElijah Newren, Jul 25, 2019
  29. Johannes SchindelinJul 25, 2019
  30. 06/19 Change call signature of write_tree_from_memory()Elijah Newren, Jul 25, 2019
  31. Johannes SchindelinJul 25, 2019
  32. Elijah NewrenJul 26, 2019
  33. 14/19 merge-recursive: comment and reorder the merge_options fieldsElijah Newren, Jul 25, 2019
  34. Junio C HamanoJul 25, 2019
  35. Elijah NewrenJul 25, 2019
  36. Junio C HamanoJul 25, 2019
  37. Johannes SchindelinJul 26, 2019
  38. Johannes SchindelinJul 25, 2019
  39. Elijah NewrenJul 26, 2019
  40. 00/20 Cleanup merge APIElijah Newren, Jul 26, 2019
  41. 01/20 merge-recursive: fix minor memory leak in error conditionElijah Newren, Jul 26, 2019
  42. Junio C HamanoJul 26, 2019
  43. Elijah NewrenJul 26, 2019
  44. Derrick StoleeJul 29, 2019
  45. 03/20 Ensure index matches head before invoking merge machinery, round NElijah Newren, Jul 26, 2019
  46. Junio C HamanoJul 26, 2019
  47. Elijah NewrenJul 26, 2019
  48. 02/20 merge-recursive: remove another implicit dependency on the_repositoryElijah Newren, Jul 26, 2019
  49. Junio C HamanoJul 26, 2019
  50. 04/20 merge-recursive: exit early if index != headElijah Newren, Jul 26, 2019
  51. Junio C HamanoJul 26, 2019
  52. Elijah NewrenJul 26, 2019
  53. Junio C HamanoJul 29, 2019
  54. 05/20 merge-recursive: remove useless parameter in merge_trees()Elijah Newren, Jul 26, 2019
  55. Junio C HamanoJul 26, 2019
  56. 07/20 Use write_index_as_tree() in lieu of write_tree_from_memory()Elijah Newren, Jul 26, 2019
  57. Junio C HamanoJul 26, 2019
  58. Elijah NewrenJul 26, 2019
  59. Junio C HamanoJul 29, 2019
  60. 08/20 merge-recursive: fix some overly long linesElijah Newren, Jul 26, 2019
  61. 06/20 merge-recursive: don't force external callers to do our loggingElijah Newren, Jul 26, 2019
  62. Junio C HamanoJul 26, 2019
  63. Elijah NewrenJul 26, 2019
  64. 10/20 merge-recursive: rename 'mrtree' to 'result_tree', for clarityElijah Newren, Jul 26, 2019
  65. 11/20 merge-recursive: rename merge_options argument to opt in headerElijah Newren, Jul 26, 2019
  66. 09/20 merge-recursive: use common name for ancestors/common/base_listElijah Newren, Jul 26, 2019
  67. 14/20 merge-recursive: comment and reorder the merge_options fieldsElijah Newren, Jul 26, 2019
  68. 13/20 merge-recursive: consolidate unnecessary fields in merge_optionsElijah Newren, Jul 26, 2019
  69. 15/20 merge-recursive: avoid losing output and leaking memory holding that outputElijah Newren, Jul 26, 2019
  70. 12/20 merge-recursive: move some definitions around to clean up the headerElijah Newren, Jul 26, 2019
  71. 16/20 merge-recursive: split internal fields into a separate structElijah Newren, Jul 26, 2019
  72. 17/20 merge-recursive: alphabetize include listElijah Newren, Jul 26, 2019
  73. 18/20 merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*Elijah Newren, Jul 26, 2019
  74. 20/20 merge-recursive: provide a better label for diff3 common ancestorElijah Newren, Jul 26, 2019
  75. 19/20 merge-recursive: be consistent with assertElijah Newren, Jul 26, 2019
  76. 00/24 Clean up merge APIElijah Newren, Aug 15, 2019
  77. 01/24 merge-recursive: be consistent with assertElijah Newren, Aug 15, 2019
  78. 03/24 merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()Elijah Newren, Aug 15, 2019
  79. Junio C HamanoAug 16, 2019
  80. 02/24 checkout: provide better conflict hunk description with detached HEADElijah Newren, Aug 15, 2019
  81. 04/24 merge-recursive: provide a better label for diff3 common ancestorElijah Newren, Aug 15, 2019
  82. Junio C HamanoAug 16, 2019
  83. Elijah NewrenAug 16, 2019
  84. 05/24 merge-recursive: introduce an enum for detect_directory_renames valuesElijah Newren, Aug 15, 2019
  85. 06/24 merge-recursive: future-proof update_file_flags() against memory leaksElijah Newren, Aug 15, 2019
  86. 07/24 merge-recursive: remove another implicit dependency on the_repositoryElijah Newren, Aug 15, 2019
  87. 08/24 Ensure index matches head before invoking merge machinery, round NElijah Newren, Aug 15, 2019
  88. 09/24 merge-recursive: exit early if index != headElijah Newren, Aug 15, 2019
  89. 10/24 merge-recursive: remove useless parameter in merge_trees()Elijah Newren, Aug 15, 2019
  90. 11/24 merge-recursive: don't force external callers to do our loggingElijah Newren, Aug 15, 2019
  91. 15/24 merge-recursive: rename 'mrtree' to 'result_tree', for clarityElijah Newren, Aug 15, 2019
  92. 14/24 merge-recursive: use common name for ancestors/common/base_listElijah Newren, Aug 15, 2019
  93. 13/24 merge-recursive: fix some overly long linesElijah Newren, Aug 15, 2019
  94. 12/24 cache-tree: share code between functions writing an index as a treeElijah Newren, Aug 15, 2019
  95. Junio C HamanoAug 16, 2019
  96. Elijah NewrenAug 16, 2019
  97. 16/24 merge-recursive: rename merge_options argument to opt in headerElijah Newren, Aug 15, 2019
  98. 17/24 merge-recursive: move some definitions around to clean up the headerElijah Newren, Aug 15, 2019
  99. 19/24 merge-recursive: comment and reorder the merge_options fieldsElijah Newren, Aug 15, 2019
  100. 20/24 merge-recursive: avoid losing output and leaking memory holding that outputElijah Newren, Aug 15, 2019
  101. 24/24 merge-recursive: alphabetize include listElijah Newren, Aug 15, 2019
  102. 21/24 merge-recursive: split internal fields into a separate structElijah Newren, Aug 15, 2019
  103. SZEDER GáborAug 16, 2019
  104. Elijah NewrenAug 16, 2019
  105. Junio C HamanoAug 16, 2019
  106. 18/24 merge-recursive: consolidate unnecessary fields in merge_optionsElijah Newren, Aug 15, 2019
  107. Junio C HamanoAug 16, 2019
  108. Elijah NewrenAug 16, 2019
  109. Junio C HamanoAug 16, 2019
  110. 23/24 merge-recursive: add sanity checks for relevant merge_optionsElijah Newren, Aug 15, 2019
  111. Junio C HamanoAug 16, 2019
  112. Elijah NewrenAug 16, 2019
  113. Junio C HamanoAug 16, 2019
  114. Junio C HamanoAug 16, 2019
  115. Elijah NewrenAug 16, 2019
  116. 22/24 merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*Elijah Newren, Aug 15, 2019
  117. 00/24 Clean up merge APIElijah Newren, Aug 17, 2019
  118. 01/24 merge-recursive: be consistent with assertElijah Newren, Aug 17, 2019
  119. 02/24 checkout: provide better conflict hunk description with detached HEADElijah Newren, Aug 17, 2019
  120. 03/24 merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()Elijah Newren, Aug 17, 2019
  121. 04/24 merge-recursive: provide a better label for diff3 common ancestorElijah Newren, Aug 17, 2019
  122. Jeff KingSep 30, 2019
  123. Jeff KingSep 30, 2019
  124. merge-recursive: fix the diff3 common ancestor label for virtual commitsElijah Newren, Sep 30, 2019
  125. Elijah NewrenOct 1, 2019
  126. merge-recursive: fix the diff3 common ancestor label for virtual commitsElijah Newren, Oct 1, 2019
  127. Jeff KingOct 1, 2019
  128. merge-recursive: fix the diff3 common ancestor label for virtual commitsElijah Newren, Oct 1, 2019
  129. Junio C HamanoOct 7, 2019
  130. merge-recursive: fix the fix to the diff3 common ancestor labelElijah Newren, Oct 7, 2019
  131. Junio C HamanoOct 8, 2019
  132. Junio C HamanoOct 8, 2019
  133. Elijah NewrenOct 8, 2019
  134. 05/24 merge-recursive: introduce an enum for detect_directory_renames valuesElijah Newren, Aug 17, 2019
  135. 06/24 merge-recursive: future-proof update_file_flags() against memory leaksElijah Newren, Aug 17, 2019
  136. 09/24 merge-recursive: exit early if index != headElijah Newren, Aug 17, 2019
  137. 08/24 Ensure index matches head before invoking merge machinery, round NElijah Newren, Aug 17, 2019
  138. Johannes SchindelinSep 2, 2019
  139. Johannes SchindelinSep 3, 2019
  140. Elijah NewrenSep 3, 2019
  141. 10/24 merge-recursive: remove useless parameter in merge_trees()Elijah Newren, Aug 17, 2019
  142. 11/24 merge-recursive: don't force external callers to do our loggingElijah Newren, Aug 17, 2019
  143. 13/24 merge-recursive: fix some overly long linesElijah Newren, Aug 17, 2019
  144. 12/24 cache-tree: share code between functions writing an index as a treeElijah Newren, Aug 17, 2019
  145. 14/24 merge-recursive: use common name for ancestors/common/base_listElijah Newren, Aug 17, 2019
  146. 15/24 merge-recursive: rename 'mrtree' to 'result_tree', for clarityElijah Newren, Aug 17, 2019
  147. 17/24 merge-recursive: move some definitions around to clean up the headerElijah Newren, Aug 17, 2019
  148. 07/24 merge-recursive: remove another implicit dependency on the_repositoryElijah Newren, Aug 17, 2019
  149. 16/24 merge-recursive: rename merge_options argument to opt in headerElijah Newren, Aug 17, 2019
  150. 20/24 merge-recursive: avoid losing output and leaking memory holding that outputElijah Newren, Aug 17, 2019
  151. 21/24 merge-recursive: split internal fields into a separate structElijah Newren, Aug 17, 2019
  152. Junio C HamanoAug 19, 2019
  153. 19/24 merge-recursive: comment and reorder the merge_options fieldsElijah Newren, Aug 17, 2019
  154. 18/24 merge-recursive: consolidate unnecessary fields in merge_optionsElijah Newren, Aug 17, 2019
  155. 22/24 merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*Elijah Newren, Aug 17, 2019
  156. 24/24 merge-recursive: alphabetize include listElijah Newren, Aug 17, 2019
  157. 23/24 merge-recursive: add sanity checks for relevant merge_optionsElijah Newren, Aug 17, 2019

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.