{"thread":{"id":"51526","subject":"[PATCH 00/19] Cleanup merge API","startedAt":"2019-07-25T17:46:19Z","lastAt":"2019-10-08T16:17:00Z","messageCount":157,"participants":["Elijah Newren","Junio C Hamano","Johannes Schindelin","Derrick Stolee","SZEDER Gábor","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":19},"messages":[{"id":"379284","messageId":"20190725174611.14802-1-newren@gmail.com","threadId":"51526","inReplyTo":null,"subject":"[PATCH 00/19] Cleanup merge API","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:52Z","receivedAt":"2019-07-25T17:46:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Before writing a replacement merge strategy for recursive, I decided\nto first cleanup the merge API -- streamlining merge-recursive.h and\nmaking it more readable.  It includes some fixes I noticed along the\nway, and the last two patches were some forgotten changes of mine I\nrediscovered that had minor textual conflicts before I rebased them on\nthis series.\n\n    While there are minor textual and semantic dependencies between\n    these patches (preventing me from splitting up this series), they\n    are logically separate and can be reviewed independently.\n\nStuff I'd most welcome review on:\n  * Is cache-tree.c the right place for write_tree_from_memory()?\n    [see patch 7]  Should there be docs on how it differs from\n    write_index_as_tree(), already found in cache-tree?  What does\n    the latter even do?\n\nSome notes:\n  * Applies on master, merges cleanly to next & pu\n  * Only patches 3, 5-7 touch anything outside of merge-recursive\n  * Patches 1 & 3 are bugfixes, 2 & 4 arguably are too, but all are\n    quite minor and do not need to be part of the impending release\n    candidates or 2.23.0.\n  * I'm going to be out next week (July 29-Aug 3), so I can only\n    respond to feedback for the next couple days or it'll have to\n    wait until the 5th.\n\nStuff I didn't address:\n  * merge_recursive() empties the commit_list of merge_bases it is\n    passed, so the caller is expected to NOT use it afterwards.  Seems\n    suboptimal to place such an expectation on the caller.\n  * All current callers (3 of them?) of merge_recursive() always pass\n    it a specially created reversed-list for the merge_bases.  Some\n    history spelunking provides no details on any of these about why;\n    it appears that the 2nd and 3rd callers reversed the list because\n    the first did, and I'm guessing the first did in an attempt to\n    exactly match the git-merge-recursive.py scripts' behavior.  But\n    if the API needs them in a reverse order from what people would\n    normally expect to pass them in, shouldn't it reverse them itself\n    instead of making all callers do it?  Also, the order shouldn't\n    matter when there are no conflicts, and when there are conflicts\n    it'd only change which side of the conflict markers the made-up\n    virtual merge base would list things in.  However, we do have\n    tests with recursive virtual merge bases and which test the\n    output, and I didn't want to try to clean those all up.  Besides,\n    the current order shows nicely when commits are named things like\n    \"L1\", \"L2\", \"R1\", \"R2\" -- it's nice having a well defined left and\n    right side.  Wasn't sure what to do yet, so I just punted for now;\n    this series is already long enough...\n\n\nElijah Newren (19):\n  merge-recursive: fix minor memory leak in error condition\n  merge-recursive: remove another implicit dependency on the_repository\n  Ensure index matches head before invoking merge machinery, round N\n  merge-recursive: exit early if index != head\n  merge-recursive: don't force external callers to do our logging\n  Change call signature of write_tree_from_memory()\n  Move write_tree_from_memory() from merge-recursive to cache-tree\n  merge-recursive: fix some overly long lines\n  merge-recursive: use common name for ancestors/common/base_list\n  merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n  merge-recursive: rename merge_options argument to opt in header\n  merge-recursive: move some definitions around to clean up the header\n  merge-recursive: consolidate unnecessary fields in merge_options\n  merge-recursive: comment and reorder the merge_options fields\n  merge-recursive: split internal fields into a separate struct\n  merge-recursive.c: alphabetize include list\n  merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n  merge-recursive: be consistent with assert\n  merge-recursive: provide a better label for diff3 common ancestor\n\n builtin/checkout.c                |   2 +-\n builtin/merge-recursive.c         |   4 +\n builtin/stash.c                   |   2 +\n cache-tree.c                      |  30 ++\n cache-tree.h                      |   2 +\n merge-recursive.c                 | 517 +++++++++++++++++-------------\n merge-recursive.h                 | 119 +++----\n sequencer.c                       |   1 -\n t/t3030-merge-recursive.sh        |   9 +-\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 191 +++++++++++\n 11 files changed, 589 insertions(+), 296 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\n-- \n2.22.0.564.gb52f8ae349\n\n"},{"id":"379285","messageId":"20190725174611.14802-2-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 01/19] merge-recursive: fix minor memory leak in error condition","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:53Z","receivedAt":"2019-07-25T17:46:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Returning before freeing the allocated buffer is suboptimal; as with\nelsewhere in the same function, make sure buf gets free'd.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 12300131fc..1163508811 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -934,9 +934,11 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \n \t\tbuf = read_object_file(&contents->oid, &type, &size);\n-\t\tif (!buf)\n-\t\t\treturn err(opt, _(\"cannot read object %s '%s'\"),\n-\t\t\t\t   oid_to_hex(&contents->oid), path);\n+\t\tif (!buf) {\n+\t\t\tret = err(opt, _(\"cannot read object %s '%s'\"),\n+\t\t\t\t  oid_to_hex(&contents->oid), path);\n+\t\t\tgoto free_buf;\n+\t\t}\n \t\tif (type != OBJ_BLOB) {\n \t\t\tret = err(opt, _(\"blob expected for %s '%s'\"),\n \t\t\t\t  oid_to_hex(&contents->oid), path);\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379286","messageId":"20190725174611.14802-3-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 02/19] merge-recursive: remove another implicit dependency on the_repository","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:54Z","receivedAt":"2019-07-25T17:46:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Commit d7cf3a96e9a0 (\"merge-recursive.c: remove implicit dependency on\nthe_repository\", 2019-01-12) and follow-ups like commit 34e7771bc644\n(\"Use the right 'struct repository' instead of the_repository\",\n2019-06-27), removed most implicit uses of the_repository.  Convert\ncalls to get_commit_tree() to instead use repo_get_commit_tree() to get\nrid of another.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1163508811..37bb94fb4d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3571,8 +3571,11 @@ int merge_recursive(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = \"merged common ancestors\";\n-\tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n-\t\t\t    get_commit_tree(merged_common_ancestors),\n+\tclean = merge_trees(opt,\n+\t\t\t    repo_get_commit_tree(opt->repo, h1),\n+\t\t\t    repo_get_commit_tree(opt->repo, h2),\n+\t\t\t    repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t merged_common_ancestors),\n \t\t\t    &mrtree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379287","messageId":"20190725174611.14802-6-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 05/19] merge-recursive: don't force external callers to do our logging","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:57Z","receivedAt":"2019-07-25T17:46:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Alternatively, you can view this as \"make the merge functions behave\nmore similarly.\"  merge-recursive has three different entry points:\nmerge_trees(), merge_recursive(), and merge_recursive_generic().  Two of\nthese would call diff_warn_rename_limit(), but merge_trees() didn't.\nThis lead to callers of merge_trees() needing to manually call\ndiff_warn_rename_limit() themselves.  Move this to the new\nmerge_finalize() function to make sure that all three entry points run\nthis function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++----\n sequencer.c       | 1 -\n 2 files changed, 3 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b762ecd7bd..7f56cb0ed1 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3583,9 +3583,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tflush_output(opt);\n \tif (!opt->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n-\tif (show(opt, 2))\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n \treturn clean;\n }\n \n@@ -3606,7 +3603,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n-\t/* Common code for wrapping up merges will be added here later */\n+\tif (show(opt, 2))\n+\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t\t       opt->needed_rename_limit, 0);\n }\n \n int merge_trees(struct merge_options *opt,\ndiff --git a/sequencer.c b/sequencer.c\nindex 34ebf8ed94..c85e594704 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -617,7 +617,6 @@ static int do_recursive_merge(struct repository *r,\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-\tdiff_warn_rename_limit(\"merge.renamelimit\", o.needed_rename_limit, 0);\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379288","messageId":"20190725174611.14802-5-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 04/19] merge-recursive: exit early if index != head","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:56Z","receivedAt":"2019-07-25T17:46:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We had a rule to enforce that the index matches head, but it was found\nat the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially huge amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nFurther, not enforcing this requirement earlier allowed other bugs (such\nas an unintentional unconditional dropping and reloading of the index in\nmerge_recursive() even when no recursion was necessary), to mask bugs in\nother callers (which were fixed in the commit prior to this one).\n\nMake sure we do the index == head check at the beginning of the merge,\nand error out immediately if it fails.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 93 +++++++++++++++++++++++++++++++++++------------\n 1 file changed, 69 insertions(+), 24 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 37bb94fb4d..b762ecd7bd 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3381,21 +3381,14 @@ static int process_entry(struct merge_options *opt,\n \treturn clean_merge;\n }\n \n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+static int merge_trees_internal(struct merge_options *opt,\n+\t\t\t\tstruct tree *head,\n+\t\t\t\tstruct tree *merge,\n+\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n-\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n-\t\t    sb.buf);\n-\t\treturn -1;\n-\t}\n \n \tif (opt->subtree_shift) {\n \t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n@@ -3499,11 +3492,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n  * Merge the commits h1 and h2, return the resulting virtual\n  * commit object and a flag indicating the cleanness of the merge.\n  */\n-int merge_recursive(struct merge_options *opt,\n-\t\t    struct commit *h1,\n-\t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n-\t\t    struct commit **result)\n+static int merge_recursive_internal(struct merge_options *opt,\n+\t\t\t\t    struct commit *h1,\n+\t\t\t\t    struct commit *h2,\n+\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_common_ancestors;\n@@ -3555,7 +3548,7 @@ int merge_recursive(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive(opt, merged_common_ancestors, iter->item,\n+\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n \t\t\t\t    NULL, &merged_common_ancestors) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n@@ -3571,12 +3564,12 @@ int merge_recursive(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = \"merged common ancestors\";\n-\tclean = merge_trees(opt,\n-\t\t\t    repo_get_commit_tree(opt->repo, h1),\n-\t\t\t    repo_get_commit_tree(opt->repo, h2),\n-\t\t\t    repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t merged_common_ancestors),\n-\t\t\t    &mrtree);\n+\tclean = merge_trees_internal(opt,\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n+\t\t\t\t     repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t     &mrtree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\n@@ -3596,6 +3589,58 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n+static int merge_start(struct merge_options *opt, struct tree *head)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tassert(opt->branch1 && opt->branch2);\n+\n+\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merge_finalize(struct merge_options *opt)\n+{\n+\t/* Common code for wrapping up merges will be added here later */\n+}\n+\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *common,\n+\t\tstruct tree **result)\n+{\n+\tint ret;\n+\n+\tif (merge_start(opt, head))\n+\t\treturn -1;\n+\tret = merge_trees_internal(opt, head, merge, common, result);\n+\tmerge_finalize(opt);\n+\n+\treturn ret;\n+}\n+\n+int merge_recursive(struct merge_options *opt,\n+\t\t    struct commit *h1,\n+\t\t    struct commit *h2,\n+\t\t    struct commit_list *ca,\n+\t\t    struct commit **result)\n+{\n+\tint ret;\n+\n+\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n+\t\treturn -1;\n+\tret = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tmerge_finalize(opt);\n+\n+\treturn ret;\n+}\n+\n static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n \t\t\t      const char *name)\n {\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379289","messageId":"20190725174611.14802-4-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 03/19] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:55Z","receivedAt":"2019-07-25T17:46:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is the bug that just won't die; there always seems to be another\nform of it somewhere.  See the commit message of 55f39cf7551b (\"merge:\nfix misleading pre-merge check documentation\", 2018-06-30) for a more\ndetailed explanation), but in short:\n\n<quick summary>\n\nbuiltin/merge.c contains this important requirement for merge\nstrategies:\n\n    ...the index must be in sync with the head commit.  The strategies are\n    responsible to ensure this.\n\nThis condition is important to enforce because there are two likely\nfailure cases when the index isn't in sync with the head commit:\n\n  * we silently throw away changes the user had staged before the merge\n\n  * we accidentally (and silently) include changes in the merge that\n    were not part of either of the branches/trees being merged\n\nDiscarding users' work and mis-merging are both bad outcomes, especially\nwhen done silently, so naturally this rule was stated sternly -- but,\nunfortunately totally ignored in practice unless and until actual bugs\nwere found.  But, fear not: the bugs from this were fixed in commit\n  ee6566e8d70d (\"[PATCH] Rewrite read-tree\", 2005-09-05)\nthrough a rewrite of read-tree (again, commit 55f39cf7551b has a more\ndetailed explanation of how this affected merge).  And it was fixed\nagain in commit\n  160252f81626 (\"git-merge-ours: make sure our index matches HEAD\", 2005-11-03)\n...and it was fixed again in commit\n  3ec62ad9ffba (\"merge-octopus: abort if index does not match HEAD\", 2016-04-09)\n...and again in commit\n  65170c07d466 (\"merge-recursive: avoid incorporating uncommitted changes in a merge\", 2017-12-21)\n...and again in commit\n  eddd1a411d93 (\"merge-recursive: enforce rule that index matches head before merging\", 2018-06-30)\n\n...with multiple testcases added to the testsuite that could be\nenumerated in even more commits.\n\nThen, finally, in a patch in the same series as the last fix above, the\ndocumentation about this requirement was fixed in commit 55f39cf7551b\n(\"merge: fix misleading pre-merge check documentation\", 2018-06-30), and\nwe all lived happily ever after...\n\n</quick summary>\n\nUnfortunately, \"ever after\" apparently denotes a limited time and it\nexpired today.  The merge-recursive rule to enforce that index matches\nhead was at the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially HUGE amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nTrying to enforce that all of merge_trees(), merge_recursive(), and\nmerge_recursive_generic() checked the index == head condition earlier\nresulted in a bunch of broken tests.  It turns out that\nmerge_recursive() has code to drop and reload the cache while recursing\nto create intermediate virtual merge bases, but unfortunately that code\nruns even when no recursion is necessary.  This unconditional dropping\nand reloading of the cache masked a few bugs:\n\n  * builtin/merge-recursive.c: didn't even bother loading the index.\n\n  * builtin/stash.c: feels like a fake 'builtin' because it repeatedly\n    invokes git subprocesses all over the place, mixed with other\n    operations.  In particular, invoking \"git reset\" will reset the\n    index on disk, but the parent process that invoked it won't\n    automatically have its in-memory index updated.\n\n  * t3030-merge-recursive.h: this test has always been broken in that it\n    didn't make sure to make index match head before running.  But, it\n    didn't care about the index or even the merge result, just the\n    verbose output while running.  While commit eddd1a411d93\n    (\"merge-recursive: enforce rule that index matches head before\n    merging\", 2018-06-30) should have uncovered this broken test, it\n    used a test_must_fail wrapper around the merge-recursive call\n    because it was known that the merge resulted in a rename/rename\n    conflict.  Thus, that fix only made this test fail for a different\n    reason, and since the index == head check didn't happen until after\n    coming all the way back out of the recursion, the testcase had\n    enough information to pass the one check that it did perform.\n\nSo, load the index in builtin/merge-recursive.c, reload the in-memory\nindex in builtin/stash.c, and modify the t3030 testcase to correctly\nsetup the index and make sure that the test fails in the expected way\n(meaning it reports a rename/rename conflict).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-recursive.c  | 4 ++++\n builtin/stash.c            | 2 ++\n t/t3030-merge-recursive.sh | 9 ++++++++-\n 3 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex 5b910e351e..a4bfd8fc51 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -1,3 +1,4 @@\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n #include \"tag.h\"\n@@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n \t\tdie(_(\"not handling anything other than two heads merge.\"));\n \n+\tif (repo_read_index_unmerged(the_repository))\n+\t\tdie_resolve_conflict(\"merge\");\n+\n \to.branch1 = argv[++i];\n \to.branch2 = argv[++i];\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex fde6397caa..bec011c1bb 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \t\t\t\treturn error(_(\"could not save index tree\"));\n \n \t\t\treset_head();\n+\t\t\tdiscard_cache();\n+\t\t\tread_cache();\n \t\t}\n \t}\n \ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex ff641b348a..a37bcc58a0 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '\n test_expect_success 'merge-recursive remembers the names of all base trees' '\n \tgit reset --hard HEAD &&\n \n+\t# make the index match $c1 so that merge-recursive below does not\n+\t# fail early\n+\tgit diff --binary HEAD $c1 -- | git apply --cached &&\n+\n \t# more trees than static slots used by oid_to_hex()\n \tfor commit in $c0 $c2 $c4 $c5 $c6 $c7\n \tdo\n \t\tgit rev-parse \"$commit^{tree}\"\n \tdone >trees &&\n \n-\t# ignore the return code -- it only fails because the input is weird\n+\t# ignore the return code; it only fails because the input is weird...\n \ttest_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&\n \n+\t# ...but make sure it fails in the expected way\n+\ttest_i18ngrep CONFLICT.*rename/rename out &&\n+\n \t# merge-recursive prints in reverse order, but we do not care\n \tsort <trees >expect &&\n \tsed -n \"s/^virtual //p\" out | sort >actual &&\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379290","messageId":"20190725174611.14802-8-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 07/19] Move write_tree_from_memory() from merge-recursive to cache-tree","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:59Z","receivedAt":"2019-07-25T17:46:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No code changes, just moving the function to what seems like a more\nlogical place.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n cache-tree.c      | 30 ++++++++++++++++++++++++++++++\n cache-tree.h      |  2 ++\n merge-recursive.c | 30 ------------------------------\n merge-recursive.h |  1 -\n 4 files changed, 32 insertions(+), 31 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 706ffcf188..d2baf6baf5 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -659,6 +659,36 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \treturn ret;\n }\n \n+struct tree *write_tree_from_memory(struct repository *repo)\n+{\n+\tstruct tree *result = NULL;\n+\tstruct index_state *istate = repo->index;\n+\n+\tif (unmerged_index(istate)) {\n+\t\tint i;\n+\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n+\t\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\t\tif (ce_stage(ce))\n+\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n+\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n+\t\t}\n+\t\tBUG(\"unmerged index entries in merge-recursive.c\");\n+\t}\n+\n+\tif (!istate->cache_tree)\n+\t\tistate->cache_tree = cache_tree();\n+\n+\tif (!cache_tree_fully_valid(istate->cache_tree) &&\n+\t    cache_tree_update(istate, 0) < 0) {\n+\t\tBUG(\"error building trees\");\n+\t}\n+\n+\tresult = lookup_tree(repo, &istate->cache_tree->oid);\n+\n+\treturn result;\n+}\n+\n static void prime_cache_tree_rec(struct repository *r,\n \t\t\t\t struct cache_tree *it,\n \t\t\t\t struct tree *tree)\ndiff --git a/cache-tree.h b/cache-tree.h\nindex 757bbc48bc..4d67c237ba 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -47,6 +47,8 @@ void cache_tree_verify(struct repository *, struct index_state *);\n #define WRITE_TREE_PREFIX_ERROR (-3)\n \n int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix);\n+struct tree *write_tree_from_memory(struct repository *repo);\n+\n void prime_cache_tree(struct repository *, struct index_state *, struct tree *);\n \n int cache_tree_matches_traversal(struct cache_tree *, struct name_entry *ent, struct traverse_info *info);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1a3c6ab7f3..937a816288 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -412,36 +412,6 @@ static void unpack_trees_finish(struct merge_options *opt)\n \tclear_unpack_trees_porcelain(&opt->unpack_opts);\n }\n \n-struct tree *write_tree_from_memory(struct repository *repo)\n-{\n-\tstruct tree *result = NULL;\n-\tstruct index_state *istate = repo->index;\n-\n-\tif (unmerged_index(istate)) {\n-\t\tint i;\n-\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n-\t\tfor (i = 0; i < istate->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = istate->cache[i];\n-\t\t\tif (ce_stage(ce))\n-\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n-\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n-\t\t}\n-\t\tBUG(\"unmerged index entries in merge-recursive.c\");\n-\t}\n-\n-\tif (!istate->cache_tree)\n-\t\tistate->cache_tree = cache_tree();\n-\n-\tif (!cache_tree_fully_valid(istate->cache_tree) &&\n-\t    cache_tree_update(istate, 0) < 0) {\n-\t\tBUG(\"error building trees\");\n-\t}\n-\n-\tresult = lookup_tree(repo, &istate->cache_tree->oid);\n-\n-\treturn result;\n-}\n-\n static int save_files_dirs(const struct object_id *oid,\n \t\t\t   struct strbuf *base, const char *path,\n \t\t\t   unsigned int mode, int stage, void *context)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 33f3d53c09..973f2729a8 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -97,7 +97,6 @@ int merge_recursive_generic(struct merge_options *o,\n \n void init_merge_options(struct merge_options *o,\n \t\t\tstruct repository *repo);\n-struct tree *write_tree_from_memory(struct repository *repo);\n \n int parse_merge_opt(struct merge_options *out, const char *s);\n \n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379291","messageId":"20190725174611.14802-9-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 08/19] merge-recursive: fix some overly long lines","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:00Z","receivedAt":"2019-07-25T17:46:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code change, just add some line breaks to fix lines that\nhave grown in length due to various refactorings.  Most remaining lines\nof excessive length in merge-recursive include error messages and it's\nnot clear that splitting those improves things.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 19 +++++++++++++------\n 1 file changed, 13 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 937a816288..104c70b787 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -681,7 +681,9 @@ static void add_flattened_path(struct strbuf *out, const char *s)\n \t\t\tout->buf[i] = '_';\n }\n \n-static char *unique_path(struct merge_options *opt, const char *path, const char *branch)\n+static char *unique_path(struct merge_options *opt,\n+\t\t\t const char *path,\n+\t\t\t const char *branch)\n {\n \tstruct path_hashmap_entry *entry;\n \tstruct strbuf newpath = STRBUF_INIT;\n@@ -915,7 +917,8 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \t\tif (S_ISREG(contents->mode)) {\n \t\t\tstruct strbuf strbuf = STRBUF_INIT;\n-\t\t\tif (convert_to_working_tree(opt->repo->index, path, buf, size, &strbuf)) {\n+\t\t\tif (convert_to_working_tree(opt->repo->index,\n+\t\t\t\t\t\t    path, buf, size, &strbuf)) {\n \t\t\t\tfree(buf);\n \t\t\t\tsize = strbuf.len;\n \t\t\t\tbuf = strbuf_detach(&strbuf, NULL);\n@@ -3392,7 +3395,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp, NULL, 512);\n+\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n \n@@ -3498,7 +3502,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n+\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n+\t\t\t\t\t\t\t      tree, \"ancestor\");\n \t}\n \n \tfor (iter = ca; iter; iter = iter->next) {\n@@ -3609,7 +3614,8 @@ int merge_recursive(struct merge_options *opt,\n \treturn ret;\n }\n \n-static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n+static struct commit *get_ref(struct repository *repo,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const char *name)\n {\n \tstruct object *object;\n@@ -3644,7 +3650,8 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\tint i;\n \t\tfor (i = 0; i < num_base_list; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i], oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n+\t\t\t\t\t     oid_to_hex(base_list[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n \t\t\t\t\t   oid_to_hex(base_list[i]));\n \t\t\tcommit_list_insert(base, &ca);\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379292","messageId":"20190725174611.14802-10-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 09/19] merge-recursive: use common name for ancestors/common/base_list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:01Z","receivedAt":"2019-07-25T17:46:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees(), merge_recursive(), and merge_recursive_generic() in\ntheir function headers used four different names for the merge base or\nlist of merge bases they were passed:\n  * 'common'\n  * 'ancestors'\n  * 'ca'\n  * 'base_list'\nThey were able to refer to it four different ways instead of only three\nby using a different name in the signature for the .c file than the .h\nfile.  Change all of these to 'merge_base' or 'merge_bases'.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 68 ++++++++++++++++++++++++-----------------------\n merge-recursive.h |  8 +++---\n 2 files changed, 39 insertions(+), 37 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 104c70b787..0a90546824 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3356,24 +3356,26 @@ static int process_entry(struct merge_options *opt,\n static int merge_trees_internal(struct merge_options *opt,\n \t\t\t\tstruct tree *head,\n \t\t\t\tstruct tree *merge,\n-\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree *merge_base,\n \t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n \n \tif (opt->subtree_shift) {\n-\t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n-\t\tcommon = shift_tree_object(opt->repo, head, common, opt->subtree_shift);\n+\t\tmerge = shift_tree_object(opt->repo, head, merge,\n+\t\t\t\t\t  opt->subtree_shift);\n+\t\tmerge_base = shift_tree_object(opt->repo, head, merge_base,\n+\t\t\t\t\t       opt->subtree_shift);\n \t}\n \n-\tif (oid_eq(&common->object.oid, &merge->object.oid)) {\n+\tif (oid_eq(&merge_base->object.oid, &merge->object.oid)) {\n \t\toutput(opt, 0, _(\"Already up to date!\"));\n \t\t*result = head;\n \t\treturn 1;\n \t}\n \n-\tcode = unpack_trees_start(opt, common, head, merge);\n+\tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(opt, 4) || opt->call_depth)\n@@ -3401,7 +3403,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tget_files_dirs(opt, merge);\n \n \t\tentries = get_unmerged(opt->repo->index);\n-\t\tclean = detect_and_process_renames(opt, common, head, merge,\n+\t\tclean = detect_and_process_renames(opt, merge_base, head, merge,\n \t\t\t\t\t\t   entries, &re_info);\n \t\trecord_df_conflict_files(opt, entries);\n \t\tif (clean < 0)\n@@ -3468,11 +3470,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t    struct commit *h1,\n \t\t\t\t    struct commit *h2,\n-\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit_list *merge_bases,\n \t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n-\tstruct commit *merged_common_ancestors;\n+\tstruct commit *merged_merge_bases;\n \tstruct tree *mrtree;\n \tint clean;\n \n@@ -3482,31 +3484,31 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\toutput_commit_title(opt, h2);\n \t}\n \n-\tif (!ca) {\n-\t\tca = get_merge_bases(h1, h2);\n-\t\tca = reverse_commit_list(ca);\n+\tif (!merge_bases) {\n+\t\tmerge_bases = get_merge_bases(h1, h2);\n+\t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \n \tif (show(opt, 5)) {\n-\t\tunsigned cnt = commit_list_count(ca);\n+\t\tunsigned cnt = commit_list_count(merge_bases);\n \n \t\toutput(opt, 5, Q_(\"found %u common ancestor:\",\n \t\t\t\t\"found %u common ancestors:\", cnt), cnt);\n-\t\tfor (iter = ca; iter; iter = iter->next)\n+\t\tfor (iter = merge_bases; iter; iter = iter->next)\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n-\tmerged_common_ancestors = pop_commit(&ca);\n-\tif (merged_common_ancestors == NULL) {\n+\tmerged_merge_bases = pop_commit(&merge_bases);\n+\tif (merged_merge_bases == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n-\t\t\t\t\t\t\t      tree, \"ancestor\");\n+\t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n+\t\t\t\t\t\t\t \"ancestor\");\n \t}\n \n-\tfor (iter = ca; iter; iter = iter->next) {\n+\tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n \t\topt->call_depth++;\n \t\t/*\n@@ -3522,14 +3524,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n-\t\t\t\t    NULL, &merged_common_ancestors) < 0)\n+\t\tif (merge_recursive_internal(opt, merged_merge_bases, iter->item,\n+\t\t\t\t\t     NULL, &merged_merge_bases) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->call_depth--;\n \n-\t\tif (!merged_common_ancestors)\n+\t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n@@ -3542,7 +3544,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &mrtree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n@@ -3585,14 +3587,14 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n+\t\tstruct tree *merge_base,\n \t\tstruct tree **result)\n {\n \tint ret;\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tret = merge_trees_internal(opt, head, merge, common, result);\n+\tret = merge_trees_internal(opt, head, merge, merge_base, result);\n \tmerge_finalize(opt);\n \n \treturn ret;\n@@ -3601,14 +3603,14 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result)\n {\n \tint ret;\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n-\tret = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tret = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n \tmerge_finalize(opt);\n \n \treturn ret;\n@@ -3636,8 +3638,8 @@ static struct commit *get_ref(struct repository *repo,\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_base_list,\n-\t\t\t    const struct object_id **base_list,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result)\n {\n \tint clean;\n@@ -3646,14 +3648,14 @@ int merge_recursive_generic(struct merge_options *opt,\n \tstruct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);\n \tstruct commit_list *ca = NULL;\n \n-\tif (base_list) {\n+\tif (merge_bases) {\n \t\tint i;\n-\t\tfor (i = 0; i < num_base_list; ++i) {\n+\t\tfor (i = 0; i < num_merge_bases; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n-\t\t\t\t\t     oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, merge_bases[i],\n+\t\t\t\t\t     oid_to_hex(merge_bases[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n-\t\t\t\t\t   oid_to_hex(base_list[i]));\n+\t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t}\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 973f2729a8..a96c8ad50f 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -74,14 +74,14 @@ static inline int merge_detect_rename(struct merge_options *o)\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ancestors,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /* rename-detecting three-way merge, no recursion */\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n+\t\tstruct tree *merge_base,\n \t\tstruct tree **result);\n \n /*\n@@ -91,8 +91,8 @@ int merge_trees(struct merge_options *o,\n int merge_recursive_generic(struct merge_options *o,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_ca,\n-\t\t\t    const struct object_id **ca,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n void init_merge_options(struct merge_options *o,\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379293","messageId":"20190725174611.14802-11-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 10/19] merge-recursive: rename 'mrtree' to 'result_tree', for clarity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:02Z","receivedAt":"2019-07-25T17:46:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"It is not at all clear what 'mr' was supposed to stand for, at least not\nto me.  Pick a clearer name for this variable.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0a90546824..61faa26c4f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3475,7 +3475,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_merge_bases;\n-\tstruct tree *mrtree;\n+\tstruct tree *result_tree;\n \tint clean;\n \n \tif (show(opt, 4)) {\n@@ -3545,14 +3545,15 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n \t\t\t\t\t\t\t  merged_merge_bases),\n-\t\t\t\t     &mrtree);\n+\t\t\t\t     &result_tree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\n \t}\n \n \tif (opt->call_depth) {\n-\t\t*result = make_virtual_commit(opt->repo, mrtree, \"merged tree\");\n+\t\t*result = make_virtual_commit(opt->repo, result_tree,\n+\t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379294","messageId":"20190725174611.14802-12-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 11/19] merge-recursive: rename merge_options argument to opt in header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:03Z","receivedAt":"2019-07-25T17:46:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 259ccb6cc324 (\"merge-recursive: rename merge_options argument\nfrom 'o' to 'opt'\", 2019-04-05), I renamed a bunch of function\narguments in merge-recursive.c, but forgot to make that same change to\nmerge-recursive.h.  Make the two match.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.h | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex a96c8ad50f..e27c2a05bb 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -64,21 +64,21 @@ struct collision_entry {\n \tunsigned reported_already:1;\n };\n \n-static inline int merge_detect_rename(struct merge_options *o)\n+static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn o->merge_detect_rename >= 0 ? o->merge_detect_rename :\n-\t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n }\n \n /* merge_trees() but with recursive ancestor consolidation */\n-int merge_recursive(struct merge_options *o,\n+int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /* rename-detecting three-way merge, no recursion */\n-int merge_trees(struct merge_options *o,\n+int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *merge_base,\n@@ -88,16 +88,16 @@ int merge_trees(struct merge_options *o,\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n  * virtual commits and call merge_recursive() proper.\n  */\n-int merge_recursive_generic(struct merge_options *o,\n+int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *o,\n+void init_merge_options(struct merge_options *opt,\n \t\t\tstruct repository *repo);\n \n-int parse_merge_opt(struct merge_options *out, const char *s);\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n \n #endif\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379295","messageId":"20190725174611.14802-14-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 13/19] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:05Z","receivedAt":"2019-07-25T17:46:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We provided users with the ability to state whether they wanted rename\ndetection, and to put a limit on how much CPU would be spent.  Both of\nthese fields had multiple configuration parameters for setting them,\nwith one being a fallback and the other being an override.  However,\ninstead of implementing the logic for how to combine the multiple\nsource locations into the appropriate setting at config loading time,\nwe loaded and tracked both values and then made the code combine them\nevery time it wanted to check the overall value.  This had a few\nminor drawbacks:\n  * it seems more complicated than necessary\n  * it runs the risk of people using the independent settings in the\n    future and breaking the intent of how the options are used\n    together\n  * it makes merge_options more complicated than necessary for other\n    potential users of the API\n\nFix these problems by moving the logic for combining the pairs of\noptions into a single value; make it apply at time-of-config-loading\ninstead of each-time-of-use.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 27 +++++++++++----------------\n merge-recursive.h |  6 ++----\n 2 files changed, 13 insertions(+), 20 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0a944f51ae..0f8d451f2e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -385,8 +385,7 @@ static int add_cacheinfo(struct merge_options *opt,\n \n static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+\treturn (opt->detect_renames != -1) ? opt->detect_renames : 1;\n }\n \n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n@@ -1885,9 +1884,7 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \t */\n \tif (opts.detect_rename > DIFF_DETECT_RENAME)\n \t\topts.detect_rename = DIFF_DETECT_RENAME;\n-\topts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n-\t\t\t    opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n-\t\t\t    1000;\n+\topts.rename_limit = (opt->rename_limit != -1) ? opt->rename_limit : 1000;\n \topts.rename_score = opt->rename_score;\n \topts.show_rename_progress = opt->show_rename_progress;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -3711,14 +3708,14 @@ static void merge_recursive_config(struct merge_options *opt)\n {\n \tchar *value = NULL;\n \tgit_config_get_int(\"merge.verbosity\", &opt->verbosity);\n-\tgit_config_get_int(\"diff.renamelimit\", &opt->diff_rename_limit);\n-\tgit_config_get_int(\"merge.renamelimit\", &opt->merge_rename_limit);\n+\tgit_config_get_int(\"diff.renamelimit\", &opt->rename_limit);\n+\tgit_config_get_int(\"merge.renamelimit\", &opt->rename_limit);\n \tif (!git_config_get_string(\"diff.renames\", &value)) {\n-\t\topt->diff_detect_rename = git_config_rename(\"diff.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"diff.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.renames\", &value)) {\n-\t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n@@ -3741,11 +3738,9 @@ void init_merge_options(struct merge_options *opt,\n \topt->repo = repo;\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->diff_rename_limit = -1;\n-\topt->merge_rename_limit = -1;\n+\topt->rename_limit = -1;\n \topt->renormalize = 0;\n-\topt->diff_detect_rename = -1;\n-\topt->merge_detect_rename = -1;\n+\topt->detect_renames = -1;\n \topt->detect_directory_renames = 1;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n@@ -3797,16 +3792,16 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \telse if (!strcmp(s, \"no-renormalize\"))\n \t\topt->renormalize = 0;\n \telse if (!strcmp(s, \"no-renames\"))\n-\t\topt->merge_detect_rename = 0;\n+\t\topt->detect_renames = 0;\n \telse if (!strcmp(s, \"find-renames\")) {\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t\topt->rename_score = 0;\n \t}\n \telse if (skip_prefix(s, \"find-renames=\", &arg) ||\n \t\t skip_prefix(s, \"rename-threshold=\", &arg)) {\n \t\tif ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)\n \t\t\treturn -1;\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t}\n \t/*\n \t * Please update $__git_merge_strategy_options in\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 1454772528..e63483b8db 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -23,10 +23,8 @@ struct merge_options {\n \tlong xdl_opts;\n \tint verbosity;\n \tint detect_directory_renames;\n-\tint diff_detect_rename;\n-\tint merge_detect_rename;\n-\tint diff_rename_limit;\n-\tint merge_rename_limit;\n+\tint detect_renames;\n+\tint rename_limit;\n \tint rename_score;\n \tint needed_rename_limit;\n \tint show_rename_progress;\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379296","messageId":"20190725174611.14802-13-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 12/19] merge-recursive: move some definitions around to clean up the header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:04Z","receivedAt":"2019-07-25T17:46:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code changes (view this with diff --color-moved), but\na few small code cleanups:\n  * Move structs and an inline function only used by merge-recursive.c\n    into merge-recursive.c\n  * Re-order function declarations to be more logical\n  * Add or fix some explanatory comments\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 31 +++++++++++++++++++++++++++\n merge-recursive.h | 53 ++++++++++-------------------------------------\n 2 files changed, 42 insertions(+), 42 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 61faa26c4f..0a944f51ae 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -54,6 +54,24 @@ static unsigned int path_hash(const char *path)\n \treturn ignore_case ? strihash(path) : strhash(path);\n }\n \n+/*\n+ * For dir_rename_entry, directory names are stored as a full path from the\n+ * toplevel of the repository and do not include a trailing '/'.  Also:\n+ *\n+ *   dir:                original name of directory being renamed\n+ *   non_unique_new_dir: if true, could not determine new_dir\n+ *   new_dir:            final name of directory being renamed\n+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n+ *                       in get_directory_renames() for details\n+ */\n+struct dir_rename_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *dir;\n+\tunsigned non_unique_new_dir:1;\n+\tstruct strbuf new_dir;\n+\tstruct string_list possible_new_dirs;\n+};\n+\n static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t      char *dir)\n {\n@@ -92,6 +110,13 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,\n \tstring_list_init(&entry->possible_new_dirs, 0);\n }\n \n+struct collision_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *target_file;\n+\tstruct string_list source_files;\n+\tunsigned reported_already:1;\n+};\n+\n static struct collision_entry *collision_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t    char *target_file)\n {\n@@ -358,6 +383,12 @@ static int add_cacheinfo(struct merge_options *opt,\n \treturn ret;\n }\n \n+static inline int merge_detect_rename(struct merge_options *opt)\n+{\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+}\n+\n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n {\n \tparse_tree(tree);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex e27c2a05bb..1454772528 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -39,36 +39,17 @@ struct merge_options {\n \tstruct repository *repo;\n };\n \n-/*\n- * For dir_rename_entry, directory names are stored as a full path from the\n- * toplevel of the repository and do not include a trailing '/'.  Also:\n- *\n- *   dir:                original name of directory being renamed\n- *   non_unique_new_dir: if true, could not determine new_dir\n- *   new_dir:            final name of directory being renamed\n- *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n- *                       in get_directory_renames() for details\n- */\n-struct dir_rename_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *dir;\n-\tunsigned non_unique_new_dir:1;\n-\tstruct strbuf new_dir;\n-\tstruct string_list possible_new_dirs;\n-};\n+void init_merge_options(struct merge_options *opt, struct repository *repo);\n \n-struct collision_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *target_file;\n-\tstruct string_list source_files;\n-\tunsigned reported_already:1;\n-};\n+/* parse the option in s and update the relevant field of opt */\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n \n-static inline int merge_detect_rename(struct merge_options *opt)\n-{\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n-}\n+/* rename-detecting three-way merge, no recursion */\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *merge_base,\n+\t\tstruct tree **result);\n \n /* merge_trees() but with recursive ancestor consolidation */\n int merge_recursive(struct merge_options *opt,\n@@ -77,16 +58,9 @@ int merge_recursive(struct merge_options *opt,\n \t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n-/* rename-detecting three-way merge, no recursion */\n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *merge_base,\n-\t\tstruct tree **result);\n-\n /*\n- * \"git-merge-recursive\" can be fed trees; wrap them into\n- * virtual commits and call merge_recursive() proper.\n+ * merge_recursive_generic can operate on trees instead of commits, by\n+ * wrapping the trees into virtual commits, and calling merge_recursive().\n  */\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n@@ -95,9 +69,4 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *opt,\n-\t\t\tstruct repository *repo);\n-\n-int parse_merge_opt(struct merge_options *opt, const char *s);\n-\n #endif\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379297","messageId":"20190725174611.14802-16-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 15/19] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:07Z","receivedAt":"2019-07-25T17:46:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_options has several internal fields that should not be set or read\nby external callers.  This just complicates the API.  Move them into an\nopaque merge_options_internal struct that is defined only in\nmerge-recursive.c and keep these out of merge-recursive.h.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 185 +++++++++++++++++++++++++---------------------\n merge-recursive.h |  17 ++---\n 2 files changed, 106 insertions(+), 96 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a5049b06a3..c572d37b21 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -29,6 +29,15 @@\n #include \"revision.h\"\n #include \"commit-reach.h\"\n \n+struct merge_options_internal {\n+\tint call_depth;\n+\tint needed_rename_limit;\n+\tstruct hashmap current_file_dir_set;\n+\tstruct string_list df_conflict_file_set;\n+\tstruct unpack_trees_options unpack_opts;\n+\tstruct index_state orig_index;\n+};\n+\n struct path_hashmap_entry {\n \tstruct hashmap_entry e;\n \tchar path[FLEX_ARRAY];\n@@ -309,7 +318,8 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \n static int show(struct merge_options *opt, int v)\n {\n-\treturn (!opt->call_depth && opt->verbosity >= v) || opt->verbosity >= 5;\n+\treturn (!opt->priv->call_depth && opt->verbosity >= v) ||\n+\t\topt->verbosity >= 5;\n }\n \n __attribute__((format (printf, 3, 4)))\n@@ -320,7 +330,7 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)\n \tif (!show(opt, v))\n \t\treturn;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \n \tva_start(ap, fmt);\n \tstrbuf_vaddf(&opt->obuf, fmt, ap);\n@@ -335,7 +345,7 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit\n {\n \tstruct merge_remote_desc *desc;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \tdesc = merge_remote_util(commit);\n \tif (desc)\n \t\tstrbuf_addf(&opt->obuf, \"virtual %s\\n\", desc->name);\n@@ -403,43 +413,43 @@ static int unpack_trees_start(struct merge_options *opt,\n \tstruct tree_desc t[3];\n \tstruct index_state tmp_index = { NULL };\n \n-\tmemset(&opt->unpack_opts, 0, sizeof(opt->unpack_opts));\n-\tif (opt->call_depth)\n-\t\topt->unpack_opts.index_only = 1;\n+\tmemset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));\n+\tif (opt->priv->call_depth)\n+\t\topt->priv->unpack_opts.index_only = 1;\n \telse\n-\t\topt->unpack_opts.update = 1;\n-\topt->unpack_opts.merge = 1;\n-\topt->unpack_opts.head_idx = 2;\n-\topt->unpack_opts.fn = threeway_merge;\n-\topt->unpack_opts.src_index = opt->repo->index;\n-\topt->unpack_opts.dst_index = &tmp_index;\n-\topt->unpack_opts.aggressive = !merge_detect_rename(opt);\n-\tsetup_unpack_trees_porcelain(&opt->unpack_opts, \"merge\");\n+\t\topt->priv->unpack_opts.update = 1;\n+\topt->priv->unpack_opts.merge = 1;\n+\topt->priv->unpack_opts.head_idx = 2;\n+\topt->priv->unpack_opts.fn = threeway_merge;\n+\topt->priv->unpack_opts.src_index = opt->repo->index;\n+\topt->priv->unpack_opts.dst_index = &tmp_index;\n+\topt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);\n+\tsetup_unpack_trees_porcelain(&opt->priv->unpack_opts, \"merge\");\n \n \tinit_tree_desc_from_tree(t+0, common);\n \tinit_tree_desc_from_tree(t+1, head);\n \tinit_tree_desc_from_tree(t+2, merge);\n \n-\trc = unpack_trees(3, t, &opt->unpack_opts);\n+\trc = unpack_trees(3, t, &opt->priv->unpack_opts);\n \tcache_tree_free(&opt->repo->index->cache_tree);\n \n \t/*\n-\t * Update opt->repo->index to match the new results, AFTER saving a copy\n-\t * in opt->orig_index.  Update src_index to point to the saved copy.\n-\t * (verify_uptodate() checks src_index, and the original index is\n-\t * the one that had the necessary modification timestamps.)\n+\t * Update opt->repo->index to match the new results, AFTER saving a\n+\t * copy in opt->priv->orig_index.  Update src_index to point to the\n+\t * saved copy.  (verify_uptodate() checks src_index, and the original\n+\t * index is the one that had the necessary modification timestamps.)\n \t */\n-\topt->orig_index = *opt->repo->index;\n+\topt->priv->orig_index = *opt->repo->index;\n \t*opt->repo->index = tmp_index;\n-\topt->unpack_opts.src_index = &opt->orig_index;\n+\topt->priv->unpack_opts.src_index = &opt->priv->orig_index;\n \n \treturn rc;\n }\n \n static void unpack_trees_finish(struct merge_options *opt)\n {\n-\tdiscard_index(&opt->orig_index);\n-\tclear_unpack_trees_porcelain(&opt->unpack_opts);\n+\tdiscard_index(&opt->priv->orig_index);\n+\tclear_unpack_trees_porcelain(&opt->priv->unpack_opts);\n }\n \n static int save_files_dirs(const struct object_id *oid,\n@@ -454,7 +464,7 @@ static int save_files_dirs(const struct object_id *oid,\n \n \tFLEX_ALLOC_MEM(entry, path, base->buf, base->len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \n \tstrbuf_setlen(base, baselen);\n \treturn (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);\n@@ -585,7 +595,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t * If we're merging merge-bases, we don't want to bother with\n \t * any working directory changes.\n \t */\n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn;\n \n \t/* Ensure D/F conflicts are adjacent in the entries list. */\n@@ -597,7 +607,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \tdf_sorted_entries.cmp = string_list_df_name_compare;\n \tstring_list_sort(&df_sorted_entries);\n \n-\tstring_list_clear(&opt->df_conflict_file_set, 1);\n+\tstring_list_clear(&opt->priv->df_conflict_file_set, 1);\n \tfor (i = 0; i < df_sorted_entries.nr; i++) {\n \t\tconst char *path = df_sorted_entries.items[i].string;\n \t\tint len = strlen(path);\n@@ -613,7 +623,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t\t    len > last_len &&\n \t\t    memcmp(path, last_file, last_len) == 0 &&\n \t\t    path[last_len] == '/') {\n-\t\t\tstring_list_insert(&opt->df_conflict_file_set, last_file);\n+\t\t\tstring_list_insert(&opt->priv->df_conflict_file_set, last_file);\n \t\t}\n \n \t\t/*\n@@ -680,8 +690,8 @@ static void update_entry(struct stage_data *entry,\n static int remove_file(struct merge_options *opt, int clean,\n \t\t       const char *path, int no_wd)\n {\n-\tint update_cache = opt->call_depth || clean;\n-\tint update_working_directory = !opt->call_depth && !no_wd;\n+\tint update_cache = opt->priv->call_depth || clean;\n+\tint update_working_directory = !opt->priv->call_depth && !no_wd;\n \n \tif (update_cache) {\n \t\tif (remove_file_from_index(opt->repo->index, path))\n@@ -724,16 +734,16 @@ static char *unique_path(struct merge_options *opt,\n \tadd_flattened_path(&newpath, branch);\n \n \tbase_len = newpath.len;\n-\twhile (hashmap_get_from_hash(&opt->current_file_dir_set,\n+\twhile (hashmap_get_from_hash(&opt->priv->current_file_dir_set,\n \t\t\t\t     path_hash(newpath.buf), newpath.buf) ||\n-\t       (!opt->call_depth && file_exists(newpath.buf))) {\n+\t       (!opt->priv->call_depth && file_exists(newpath.buf))) {\n \t\tstrbuf_setlen(&newpath, base_len);\n \t\tstrbuf_addf(&newpath, \"_%d\", suffix++);\n \t}\n \n \tFLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \treturn strbuf_detach(&newpath, NULL);\n }\n \n@@ -775,7 +785,7 @@ static int dir_in_way(struct index_state *istate, const char *path,\n static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\t\t\t   const struct diff_filespec *blob)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \tstruct cache_entry *ce;\n \n \tif (0 > pos)\n@@ -783,7 +793,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\treturn 0;\n \n \t/* See if the file we were tracking before matches */\n-\tce = opt->orig_index.cache[pos];\n+\tce = opt->priv->orig_index.cache[pos];\n \treturn (oid_eq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);\n }\n \n@@ -792,7 +802,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n  */\n static int was_tracked(struct merge_options *opt, const char *path)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \n \tif (0 <= pos)\n \t\t/* we were tracking this path before the merge */\n@@ -849,12 +859,12 @@ static int was_dirty(struct merge_options *opt, const char *path)\n \tstruct cache_entry *ce;\n \tint dirty = 1;\n \n-\tif (opt->call_depth || !was_tracked(opt, path))\n+\tif (opt->priv->call_depth || !was_tracked(opt, path))\n \t\treturn !dirty;\n \n-\tce = index_file_exists(opt->unpack_opts.src_index,\n+\tce = index_file_exists(opt->priv->unpack_opts.src_index,\n \t\t\t       path, strlen(path), ignore_case);\n-\tdirty = verify_uptodate(ce, &opt->unpack_opts) != 0;\n+\tdirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;\n \treturn dirty;\n }\n \n@@ -864,8 +874,8 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \tconst char *msg = _(\"failed to create path '%s'%s\");\n \n \t/* Unlink any D/F conflict files that are in the way */\n-\tfor (i = 0; i < opt->df_conflict_file_set.nr; i++) {\n-\t\tconst char *df_path = opt->df_conflict_file_set.items[i].string;\n+\tfor (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {\n+\t\tconst char *df_path = opt->priv->df_conflict_file_set.items[i].string;\n \t\tsize_t pathlen = strlen(path);\n \t\tsize_t df_pathlen = strlen(df_path);\n \t\tif (df_pathlen < pathlen &&\n@@ -875,7 +885,7 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \t\t\t       _(\"Removing %s to make room for subdirectory\\n\"),\n \t\t\t       df_path);\n \t\t\tunlink(df_path);\n-\t\t\tunsorted_string_list_delete_item(&opt->df_conflict_file_set,\n+\t\t\tunsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,\n \t\t\t\t\t\t\t i, 0);\n \t\t\tbreak;\n \t\t}\n@@ -916,7 +926,7 @@ static int update_file_flags(struct merge_options *opt,\n {\n \tint ret = 0;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\tupdate_wd = 0;\n \n \tif (update_wd) {\n@@ -1001,7 +1011,7 @@ static int update_file(struct merge_options *opt,\n \t\t       const char *path)\n {\n \treturn update_file_flags(opt, contents, path,\n-\t\t\t\t opt->call_depth || clean, !opt->call_depth);\n+\t\t\t\t opt->priv->call_depth || clean, !opt->priv->call_depth);\n }\n \n /* Low level file merging, update and removal */\n@@ -1030,7 +1040,7 @@ static int merge_3way(struct merge_options *opt,\n \tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = opt->xdl_opts;\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\tll_opts.virtual_ancestor = 1;\n \t\tll_opts.variant = 0;\n \t} else {\n@@ -1164,7 +1174,7 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct object_array merges;\n \n \tint i;\n-\tint search = !opt->call_depth;\n+\tint search = !opt->priv->call_depth;\n \n \t/* store a in result in case we fail */\n \toidcpy(result, a);\n@@ -1385,7 +1395,7 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \tint mark_conflicted = (opt->detect_directory_renames == 1);\n \tassert(ren->dir_rename_original_dest);\n \n-\tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n+\tif (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) {\n \t\tmark_conflicted = 1;\n \t\tfile_path = unique_path(opt, dest->path, ren->branch);\n \t\toutput(opt, 1, _(\"Error: Refusing to lose untracked file at %s; \"\n@@ -1428,12 +1438,12 @@ static int handle_change_delete(struct merge_options *opt,\n \tconst char *update_path = path;\n \tint ret = 0;\n \n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0) ||\n-\t    (!opt->call_depth && would_lose_untracked(opt, path))) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) ||\n+\t    (!opt->priv->call_depth && would_lose_untracked(opt, path))) {\n \t\tupdate_path = alt_path = unique_path(opt, path, change_branch);\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * We cannot arbitrarily accept either a_sha or b_sha as\n \t\t * correct; since there is no true \"middle point\" between\n@@ -1508,14 +1518,14 @@ static int handle_rename_delete(struct merge_options *opt,\n \t\t\t\t     opt->branch2 : opt->branch1);\n \n \tif (handle_change_delete(opt,\n-\t\t\t\t opt->call_depth ? orig->path : dest->path,\n-\t\t\t\t opt->call_depth ? NULL : orig->path,\n+\t\t\t\t opt->priv->call_depth ? orig->path : dest->path,\n+\t\t\t\t opt->priv->call_depth ? NULL : orig->path,\n \t\t\t\t orig, dest,\n \t\t\t\t rename_branch, delete_branch,\n \t\t\t\t _(\"rename\"), _(\"renamed\")))\n \t\treturn -1;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn remove_file_from_index(opt->repo->index, dest->path);\n \telse\n \t\treturn update_stages(opt, dest->path, NULL,\n@@ -1552,7 +1562,7 @@ static int handle_file_collision(struct merge_options *opt,\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n-\tif (opt->call_depth && (prev_path1 || prev_path2)) {\n+\tif (opt->priv->call_depth && (prev_path1 || prev_path2)) {\n \t\t/* Put first file (a->oid, a->mode) in its original spot */\n \t\tif (prev_path1) {\n \t\t\tif (update_file(opt, 1, a, prev_path1))\n@@ -1581,10 +1591,10 @@ static int handle_file_collision(struct merge_options *opt,\n \t/* Remove rename sources if rename/add or rename/rename(2to1) */\n \tif (prev_path1)\n \t\tremove_file(opt, 1, prev_path1,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path1));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path1));\n \tif (prev_path2)\n \t\tremove_file(opt, 1, prev_path2,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path2));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path2));\n \n \t/*\n \t * Remove the collision path, if it wouldn't cause dirty contents\n@@ -1626,12 +1636,12 @@ static int handle_file_collision(struct merge_options *opt,\n \tnull.mode = 0;\n \n \tif (merge_mode_and_contents(opt, &null, a, b, collide_path,\n-\t\t\t\t    branch1, branch2, opt->call_depth * 2, &mfi))\n+\t\t\t\t    branch1, branch2, opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tmfi.clean &= !alt_path;\n \tif (update_file(opt, mfi.clean, &mfi.blob, update_path))\n \t\treturn -1;\n-\tif (!mfi.clean && !opt->call_depth &&\n+\tif (!mfi.clean && !opt->priv->call_depth &&\n \t    update_stages(opt, collide_path, NULL, a, b))\n \t\treturn -1;\n \tfree(alt_path);\n@@ -1671,7 +1681,7 @@ static int handle_rename_add(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[other_stage],\n \t\t\t\t    prev_path_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(prev_path_desc);\n \n@@ -1689,7 +1699,7 @@ static char *find_path_for_conflict(struct merge_options *opt,\n \t\t\t\t    const char *branch2)\n {\n \tchar *new_path = NULL;\n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0)) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) {\n \t\tnew_path = unique_path(opt, path, branch1);\n \t\toutput(opt, 1, _(\"%s is a directory in %s adding \"\n \t\t\t       \"as %s instead\"),\n@@ -1720,17 +1730,17 @@ static int handle_rename_rename_1to2(struct merge_options *opt,\n \t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\"),\n \t       o->path, a->path, ci->ren1->branch,\n \t       o->path, b->path, ci->ren2->branch,\n-\t       opt->call_depth ? _(\" (left unresolved)\") : \"\");\n+\t       opt->priv->call_depth ? _(\" (left unresolved)\") : \"\");\n \n \tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n \t\t\t    a->path, b->path, o->path);\n \tif (merge_mode_and_contents(opt, o, a, b, path_desc,\n \t\t\t\t    ci->ren1->branch, ci->ren2->branch,\n-\t\t\t\t    opt->call_depth * 2, &mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(path_desc);\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1845,12 +1855,12 @@ static int handle_rename_rename_2to1(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[ostage1],\n \t\t\t\t    path_side_1_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(opt, b,\n \t\t\t\t    &ci->ren2->src_entry->stages[ostage2],\n \t\t\t\t    c2, path_side_2_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -1891,8 +1901,8 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \tdiff_setup_done(&opts);\n \tdiff_tree_oid(&o_tree->object.oid, &tree->object.oid, \"\", &opts);\n \tdiffcore_std(&opts);\n-\tif (opts.needed_rename_limit > opt->needed_rename_limit)\n-\t\topt->needed_rename_limit = opts.needed_rename_limit;\n+\tif (opts.needed_rename_limit > opt->priv->needed_rename_limit)\n+\t\topt->priv->needed_rename_limit = opts.needed_rename_limit;\n \n \tret = xmalloc(sizeof(*ret));\n \t*ret = diff_queued_diff;\n@@ -3022,13 +3032,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\treason = _(\"add/add\");\n \n \tassert(o->path && a->path && b->path);\n-\tif (ci && dir_in_way(opt->repo->index, path, !opt->call_depth,\n+\tif (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth,\n \t\t\t     S_ISGITLINK(ci->ren1->pair->two->mode)))\n \t\tdf_conflict_remains = 1;\n \n \tif (merge_mode_and_contents(opt, o, a, b, path,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    opt->call_depth * 2, mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, mfi))\n \t\treturn -1;\n \n \t/*\n@@ -3044,7 +3054,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \t\toutput(opt, 3, _(\"Skipped %s (merged same as existing)\"), path);\n \t\tif (add_cacheinfo(opt, &mfi->blob, path,\n-\t\t\t\t  0, (!opt->call_depth && !is_dirty), 0))\n+\t\t\t\t  0, (!opt->priv->call_depth && !is_dirty), 0))\n \t\t\treturn -1;\n \t\t/*\n \t\t * However, add_cacheinfo() will delete the old cache entry\n@@ -3052,8 +3062,8 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\t * flag to avoid making the file appear as if it were\n \t\t * deleted by the user.\n \t\t */\n-\t\tpos = index_name_pos(&opt->orig_index, path, strlen(path));\n-\t\tce = opt->orig_index.cache[pos];\n+\t\tpos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n+\t\tce = opt->priv->orig_index.cache[pos];\n \t\tif (ce_skip_worktree(ce)) {\n \t\t\tpos = index_name_pos(opt->repo->index, path, strlen(path));\n \t\t\tce = opt->repo->index->cache[pos];\n@@ -3074,7 +3084,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \tif (df_conflict_remains || is_dirty) {\n \t\tchar *new_path;\n-\t\tif (opt->call_depth) {\n+\t\tif (opt->priv->call_depth) {\n \t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t} else {\n \t\t\tif (!mfi->clean) {\n@@ -3332,7 +3342,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\tconf = _(\"directory/file\");\n \t\t}\n \t\tif (dir_in_way(opt->repo->index, path,\n-\t\t\t       !opt->call_depth && !S_ISGITLINK(a->mode),\n+\t\t\t       !opt->priv->call_depth && !S_ISGITLINK(a->mode),\n \t\t\t       0)) {\n \t\t\tchar *new_path = unique_path(opt, path, add_branch);\n \t\t\tclean_merge = 0;\n@@ -3341,7 +3351,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tif (update_file(opt, 0, contents, new_path))\n \t\t\t\tclean_merge = -1;\n-\t\t\telse if (opt->call_depth)\n+\t\t\telse if (opt->priv->call_depth)\n \t\t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t\tfree(new_path);\n \t\t} else {\n@@ -3406,7 +3416,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n-\t\tif (show(opt, 4) || opt->call_depth)\n+\t\tif (show(opt, 4) || opt->priv->call_depth)\n \t\t\terr(opt, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n@@ -3425,7 +3435,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\thashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp,\n \t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n@@ -3462,7 +3472,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free(&opt->current_file_dir_set, 1);\n+\t\thashmap_free(&opt->priv->current_file_dir_set, 1);\n \n \t\tif (clean < 0) {\n \t\t\tunpack_trees_finish(opt);\n@@ -3474,7 +3484,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth && !(*result = write_tree_from_memory(opt->repo)))\n+\tif (opt->priv->call_depth &&\n+\t    !(*result = write_tree_from_memory(opt->repo)))\n \t\treturn -1;\n \n \treturn clean;\n@@ -3538,7 +3549,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \n \tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n-\t\topt->call_depth++;\n+\t\topt->priv->call_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n \t\t * with conflict markers. The cleanness flag is\n@@ -3557,14 +3568,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n-\t\topt->call_depth--;\n+\t\topt->priv->call_depth--;\n \n \t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n \tdiscard_index(opt->repo->index);\n-\tif (!opt->call_depth)\n+\tif (!opt->priv->call_depth)\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = \"merged common ancestors\";\n@@ -3579,14 +3590,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\treturn clean;\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t*result = make_virtual_commit(opt->repo, result_tree,\n \t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n \tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n+\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n \treturn clean;\n }\n@@ -3603,6 +3614,8 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \t\treturn -1;\n \t}\n \n+\topt->priv = xcalloc(1, sizeof(*opt->priv));\n+\tstring_list_init(&opt->priv->df_conflict_file_set, 1);\n \treturn 0;\n }\n \n@@ -3610,7 +3623,9 @@ static void merge_finalize(struct merge_options *opt)\n {\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0);\n+\tfree(opt->priv);\n+\topt->priv = NULL;\n }\n \n int merge_trees(struct merge_options *opt,\n@@ -3748,8 +3763,6 @@ void init_merge_options(struct merge_options *opt,\n \n \topt->renormalize = 0;\n \n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n-\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex d57fce0daa..a1bb29dc33 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -1,13 +1,15 @@\n #ifndef MERGE_RECURSIVE_H\n #define MERGE_RECURSIVE_H\n \n-#include \"string-list.h\"\n-#include \"unpack-trees.h\"\n+#include \"strbuf.h\"\n \n struct commit;\n-\n+struct commit_list;\n+struct object_id;\n struct repository;\n+struct tree;\n \n+struct merge_options_internal;\n struct merge_options {\n \tstruct repository *repo;\n \n@@ -40,13 +42,8 @@ struct merge_options {\n \tconst char *subtree_shift;\n \tunsigned renormalize : 1;\n \n-\t/* internal fields used by the implementation (do NOT set these) */\n-\tint call_depth;\n-\tint needed_rename_limit;\n-\tstruct hashmap current_file_dir_set;\n-\tstruct string_list df_conflict_file_set;\n-\tstruct unpack_trees_options unpack_opts;\n-\tstruct index_state orig_index;\n+\t/* internal fields used by the implementation */\n+\tstruct merge_options_internal *priv;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379298","messageId":"20190725174611.14802-18-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 17/19] merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:09Z","receivedAt":"2019-07-25T17:46:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"I want to implement the same outward facing API as found within\nmerge-recursive.h in a different merge strategy.  However, that makes\nnames like MERGE_RECURSIVE_{NORMAL,OURS,THEIRS} look a little funny;\nrename to MERGE_VARIANT_{NORMAL,OURS,THEIRS}.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 14 +++++++-------\n merge-recursive.h |  6 +++---\n 2 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 32889a2f0d..0f689095d9 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1046,10 +1046,10 @@ static int merge_3way(struct merge_options *opt,\n \t\tll_opts.variant = 0;\n \t} else {\n \t\tswitch (opt->recursive_variant) {\n-\t\tcase MERGE_RECURSIVE_OURS:\n+\t\tcase MERGE_VARIANT_OURS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_OURS;\n \t\t\tbreak;\n-\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n \t\t\tbreak;\n \t\tdefault:\n@@ -1359,15 +1359,15 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&b->oid);\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n-\t\t\tcase MERGE_RECURSIVE_NORMAL:\n+\t\t\tcase MERGE_VARIANT_NORMAL:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tif (!oid_eq(&a->oid, &b->oid))\n \t\t\t\t\tresult->clean = 0;\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_OURS:\n+\t\t\tcase MERGE_VARIANT_OURS:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\t\toidcpy(&result->blob.oid, &b->oid);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -3779,9 +3779,9 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \tif (!s || !*s)\n \t\treturn -1;\n \tif (!strcmp(s, \"ours\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_OURS;\n+\t\topt->recursive_variant = MERGE_VARIANT_OURS;\n \telse if (!strcmp(s, \"theirs\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_THEIRS;\n+\t\topt->recursive_variant = MERGE_VARIANT_THEIRS;\n \telse if (!strcmp(s, \"subtree\"))\n \t\topt->subtree_shift = \"\";\n \telse if (skip_prefix(s, \"subtree=\", &arg))\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex a1bb29dc33..c4e6fb85f1 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -28,9 +28,9 @@ struct merge_options {\n \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n \tlong xdl_opts;\n \tenum {\n-\t\tMERGE_RECURSIVE_NORMAL = 0,\n-\t\tMERGE_RECURSIVE_OURS,\n-\t\tMERGE_RECURSIVE_THEIRS\n+\t\tMERGE_VARIANT_NORMAL = 0,\n+\t\tMERGE_VARIANT_OURS,\n+\t\tMERGE_VARIANT_THEIRS\n \t} recursive_variant;\n \n \t/* console output related options */\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379299","messageId":"20190725174611.14802-17-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 16/19] merge-recursive: alphabetize include list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:08Z","receivedAt":"2019-07-25T17:46:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Other than cache.h which needs to appear first, and merge-recursive.h\nwhich I want to be second so that we are more likely to notice if\nmerge-recursive.h has any missing includes, the rest of the list is\nlong and easier to look through if it's alphabetical.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 33 +++++++++++++++++----------------\n 1 file changed, 17 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c572d37b21..32889a2f0d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -4,30 +4,31 @@\n  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006\n  */\n #include \"cache.h\"\n-#include \"config.h\"\n+#include \"merge-recursive.h\"\n+\n #include \"advice.h\"\n-#include \"lockfile.h\"\n-#include \"cache-tree.h\"\n-#include \"object-store.h\"\n-#include \"repository.h\"\n-#include \"commit.h\"\n+#include \"alloc.h\"\n+#include \"attr.h\"\n #include \"blob.h\"\n #include \"builtin.h\"\n-#include \"tree-walk.h\"\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"commit-reach.h\"\n+#include \"config.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"dir.h\"\n+#include \"ll-merge.h\"\n+#include \"lockfile.h\"\n+#include \"object-store.h\"\n+#include \"repository.h\"\n+#include \"revision.h\"\n+#include \"string-list.h\"\n+#include \"submodule.h\"\n #include \"tag.h\"\n-#include \"alloc.h\"\n+#include \"tree-walk.h\"\n #include \"unpack-trees.h\"\n-#include \"string-list.h\"\n #include \"xdiff-interface.h\"\n-#include \"ll-merge.h\"\n-#include \"attr.h\"\n-#include \"merge-recursive.h\"\n-#include \"dir.h\"\n-#include \"submodule.h\"\n-#include \"revision.h\"\n-#include \"commit-reach.h\"\n \n struct merge_options_internal {\n \tint call_depth;\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379300","messageId":"20190725174611.14802-19-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 18/19] merge-recursive: be consistent with assert","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:10Z","receivedAt":"2019-07-25T17:46:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 8daec1df03de (\"merge-recursive: switch from (oid,mode) pairs\nto a diff_filespec\", 2019-04-05), an assertion on a->path && b->path\nwas added for code readability to document that these both needed to be\nnon-NULL at this point in the code.  However, the subsequent lines also\nread o->path, so it should be included in the assert.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0f689095d9..4cd6599296 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1058,7 +1058,7 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path);\n+\tassert(a->path && b->path && o->path);\n \tif (strcmp(a->path, b->path) ||\n \t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n \t\tbase_name = opt->ancestor == NULL ? NULL :\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379301","messageId":"20190725174611.14802-20-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 19/19] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:11Z","receivedAt":"2019-07-25T17:46:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 7ca56aa07619 (\"merge-recursive: add a label for ancestor\",\n2010-03-20), a label was added for the '||||||' line to make it have\nthe more informative heading '|||||| merged common ancestors', with\nthe statement:\n\n    It would be nicer to use a more informative label.  Perhaps someone\n    will provide one some day.\n\nThis chosen label was perfectly reasonable when recursiveness kicks in,\ni.e. when there are multiple merge bases.  (I can't think of a better\nlabel in such cases.)  But it is actually somewhat misleading when there\nis a unique merge base or no merge base.  Change this based on the\nnumber of merge bases:\n    >=2: \"merged common ancestors\"\n    1:   <abbreviated commit hash>\n    0:   \"<empty tree>\"\n\nAlso, the code in merge_3way making use of opt->ancestor was overly\ncomplex because it tried to handle the case of opt->ancestor being NULL.\nWe always set it first, though, so just add an assert that opt->ancestor\nis not NULL and simplify the surrounding code.\n\nTests have also been added to check that we get the right ancestor name\nfor each of the three cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |  35 ++++--\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 191 ++++++++++++++++++++++++++++++\n 3 files changed, 220 insertions(+), 14 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 4cd6599296..8ac53cacdf 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1034,7 +1034,7 @@ static int merge_3way(struct merge_options *opt,\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n-\tchar *base_name, *name1, *name2;\n+\tchar *base, *name1, *name2;\n \tint merge_status;\n \n \tll_opts.renormalize = opt->renormalize;\n@@ -1058,16 +1058,13 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path && o->path);\n-\tif (strcmp(a->path, b->path) ||\n-\t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s:%s\", opt->ancestor, o->path);\n+\tassert(a->path && b->path && o->path && opt->ancestor);\n+\tif (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {\n+\t\tbase  = mkpathdup(\"%s:%s\", opt->ancestor, o->path);\n \t\tname1 = mkpathdup(\"%s:%s\", branch1, a->path);\n \t\tname2 = mkpathdup(\"%s:%s\", branch2, b->path);\n \t} else {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s\", opt->ancestor);\n+\t\tbase  = mkpathdup(\"%s\", opt->ancestor);\n \t\tname1 = mkpathdup(\"%s\", branch1);\n \t\tname2 = mkpathdup(\"%s\", branch2);\n \t}\n@@ -1076,11 +1073,11 @@ static int merge_3way(struct merge_options *opt,\n \tread_mmblob(&src1, &a->oid);\n \tread_mmblob(&src2, &b->oid);\n \n-\tmerge_status = ll_merge(result_buf, a->path, &orig, base_name,\n+\tmerge_status = ll_merge(result_buf, a->path, &orig, base,\n \t\t\t\t&src1, name1, &src2, name2,\n \t\t\t\topt->repo->index, &ll_opts);\n \n-\tfree(base_name);\n+\tfree(base);\n \tfree(name1);\n \tfree(name2);\n \tfree(orig.ptr);\n@@ -3517,6 +3514,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tstruct commit *merged_merge_bases;\n \tstruct tree *result_tree;\n \tint clean;\n+\tint num_merge_bases;\n+\tstruct strbuf commit_name = STRBUF_INIT;\n \n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n@@ -3538,6 +3537,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n+\tnum_merge_bases = commit_list_count(merge_bases);\n \tmerged_merge_bases = pop_commit(&merge_bases);\n \tif (merged_merge_bases == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n@@ -3579,13 +3579,26 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tif (!opt->priv->call_depth)\n \t\trepo_read_index(opt->repo);\n \n-\topt->ancestor = \"merged common ancestors\";\n+\tswitch (num_merge_bases) {\n+\tcase 0:\n+\t\topt->ancestor = \"<empty tree>\";\n+\t\tbreak;\n+\tcase 1:\n+\t\tstrbuf_add_unique_abbrev(&commit_name,\n+\t\t\t\t\t &merged_merge_bases->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\topt->ancestor = commit_name.buf;\n+\t\tbreak;\n+\tdefault:\n+\t\topt->ancestor = \"merged common ancestors\";\n+\t}\n \tclean = merge_trees_internal(opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n \t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &result_tree);\n+\tstrbuf_release(&commit_name);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d23b948f27..7fddcc8c73 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1562,6 +1562,7 @@ test_expect_success 'check nested conflicts' '\n \t\tcd nested_conflicts &&\n \n \t\tgit clean -f &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L2^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1582,7 +1583,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:a >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1594,7 +1595,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:b >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1732,6 +1733,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t(\n \t\tcd virtual_merge_base_has_nested_conflicts &&\n \n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L3^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1760,7 +1762,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t\tcp left merged-once &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tmerged-once \\\n \t\t\tbase        \\\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nnew file mode 100755\nindex 0000000000..08cb1668a5\n--- /dev/null\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -0,0 +1,191 @@\n+#!/bin/sh\n+\n+test_description='recursive merge diff3 style conflict markers'\n+\n+. ./test-lib.sh\n+\n+# Setup:\n+#          L1\n+#            \\\n+#             ?\n+#            /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 both have a file named 'content' but have no common history\n+#\n+\n+test_expect_success 'setup no merge base' '\n+\ttest_create_repo no_merge_base &&\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout --orphan L &&\n+\t\ttest_seq 1 9 >content &&\n+\t\techo \"A\" >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version L1 of content\" &&\n+\n+\t\t# Create R\n+\t\tgit checkout --orphan R &&\n+\t\ttest_seq 1 9 >content &&\n+\t\techo \"10\" >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version R1 of content\"\n+\t)\n+'\n+\n+test_expect_success 'check no merge base' '\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| <empty tree>\" content\n+\t)\n+'\n+\n+# Setup:\n+#          L1\n+#         /  \\\n+#   master    ?\n+#         \\  /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 have modified the same file ('content') in conflicting ways\n+#\n+\n+test_expect_success 'setup unique merge base' '\n+\ttest_create_repo unique_merge_base &&\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\ttest_seq 1 9 >content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\ttest_seq 0 9 >content &&\n+\t\techo \"A\" >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version L1 of content\" &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\ttest_seq 0 9 >content &&\n+\t\techo \"ten\" >>content &&\n+\t\tgit add content &&\n+\t\tgit mv content renamed &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version R1 of content\"\n+\t)\n+'\n+\n+test_expect_success 'check unique merge base' '\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| $MASTER:content\" renamed\n+\t)\n+'\n+\n+# Setup:\n+#          L1---L2--L3\n+#         /  \\ /      \\\n+#   master    X1       ?\n+#         \\  / \\      /\n+#          R1---R2--R3\n+#\n+# Where:\n+#   commits L1 and R1 have modified the same file in non-conflicting ways\n+#   X1 is an auto-generated merge-base used when merging L1 and R1\n+#   commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively\n+#   commits L3 and R3 both modify 'content' in conflicting ways\n+#\n+\n+test_expect_success 'setup multiple merge bases' '\n+\ttest_create_repo multiple_merge_bases &&\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\t# Create some related files now\n+\t\ttest_seq 1 9 >content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\ttest_seq 0 9 >content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version L1 of content\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\ttest_seq 1 10 >content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"verson R1 of content\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\tgit merge R1 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\tgit merge L1 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_seq 0 9 >content &&\n+\t\techo \"A\" >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version L3 of content\" &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\ttest_seq 0 9 >content &&\n+\t\techo \"ten\" >>content &&\n+\t\tgit add content &&\n+\t\tgit mv content renamed &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"version R3 of content\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple merge bases' '\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| merged common ancestors:content\" renamed\n+\t)\n+'\n+\n+test_done\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379302","messageId":"20190725174611.14802-7-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 06/19] Change call signature of write_tree_from_memory()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:45:58Z","receivedAt":"2019-07-25T17:46:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"write_tree_from_memory() is more of a cache-tree thing than a\nmerge-recursive thing (especially since it is called from checkout.c in\na context before doing anything with merging), but in particular there\nis no need for it to take a merge_options struct when it only really\nneeds a repository struct.\n\nOne small wrinkle in this is that there is a call to err(), which takes\na merge_options.  However, this did not used to be there.  In commits\n6003303a1e50 (\"merge-recursive: switch to returning errors instead of\ndying\", 2016-07-26) and 033abf97fcbc (\"Replace all die(\"BUG: ...\") calls\nby BUG() ones\", 2018-05-02), all the calls to die() were switched over\nto either err() or BUG() -- and this particular case was converted\nincorrectly; it should have been a BUG().\n\nSo, convert write_tree_from_memory()'s current call to err() to instead\ncall BUG(), and then make it take a struct repository instead of a\nstruct merge_options.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  2 +-\n merge-recursive.c  | 11 +++++------\n merge-recursive.h  |  2 +-\n 3 files changed, 7 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 91f8509f85..ec13116354 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -760,7 +760,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t */\n \t\t\tinit_merge_options(&o, the_repository);\n \t\t\to.verbosity = 0;\n-\t\t\twork = write_tree_from_memory(&o);\n+\t\t\twork = write_tree_from_memory(the_repository);\n \n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 7f56cb0ed1..1a3c6ab7f3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -412,10 +412,10 @@ static void unpack_trees_finish(struct merge_options *opt)\n \tclear_unpack_trees_porcelain(&opt->unpack_opts);\n }\n \n-struct tree *write_tree_from_memory(struct merge_options *opt)\n+struct tree *write_tree_from_memory(struct repository *repo)\n {\n \tstruct tree *result = NULL;\n-\tstruct index_state *istate = opt->repo->index;\n+\tstruct index_state *istate = repo->index;\n \n \tif (unmerged_index(istate)) {\n \t\tint i;\n@@ -434,11 +434,10 @@ struct tree *write_tree_from_memory(struct merge_options *opt)\n \n \tif (!cache_tree_fully_valid(istate->cache_tree) &&\n \t    cache_tree_update(istate, 0) < 0) {\n-\t\terr(opt, _(\"error building trees\"));\n-\t\treturn NULL;\n+\t\tBUG(\"error building trees\");\n \t}\n \n-\tresult = lookup_tree(opt->repo, &istate->cache_tree->oid);\n+\tresult = lookup_tree(repo, &istate->cache_tree->oid);\n \n \treturn result;\n }\n@@ -3471,7 +3470,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth && !(*result = write_tree_from_memory(opt)))\n+\tif (opt->call_depth && !(*result = write_tree_from_memory(opt->repo)))\n \t\treturn -1;\n \n \treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c2b7bb65c6..33f3d53c09 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -97,7 +97,7 @@ int merge_recursive_generic(struct merge_options *o,\n \n void init_merge_options(struct merge_options *o,\n \t\t\tstruct repository *repo);\n-struct tree *write_tree_from_memory(struct merge_options *o);\n+struct tree *write_tree_from_memory(struct repository *repo);\n \n int parse_merge_opt(struct merge_options *out, const char *s);\n \n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379303","messageId":"20190725174611.14802-15-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH 14/19] merge-recursive: comment and reorder the merge_options fields","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T17:46:06Z","receivedAt":"2019-07-25T17:46:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The merge_options struct had lots of fields, making it a little\nimposing, but the options naturally fall into multiple different groups.\nGrouping similar options and adding a comment or two makes it easier to\nread, easier for new folks to figure out which options are related, and\nthus easier for them to find the options they need.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 16 +++++++++++-----\n merge-recursive.h | 34 +++++++++++++++++++++++-----------\n 2 files changed, 34 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0f8d451f2e..a5049b06a3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3735,21 +3735,27 @@ void init_merge_options(struct merge_options *opt,\n {\n \tconst char *merge_verbosity;\n \tmemset(opt, 0, sizeof(struct merge_options));\n+\n \topt->repo = repo;\n+\n+\topt->detect_renames = -1;\n+\topt->detect_directory_renames = 1;\n+\topt->rename_limit = -1;\n+\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->rename_limit = -1;\n+\tstrbuf_init(&opt->obuf, 0);\n+\n \topt->renormalize = 0;\n-\topt->detect_renames = -1;\n-\topt->detect_directory_renames = 1;\n+\n+\tstring_list_init(&opt->df_conflict_file_set, 1);\n+\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\n \t\topt->verbosity = strtol(merge_verbosity, NULL, 10);\n \tif (opt->verbosity >= 5)\n \t\topt->buffer_output = 0;\n-\tstrbuf_init(&opt->obuf, 0);\n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n }\n \n int parse_merge_opt(struct merge_options *opt, const char *s)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex e63483b8db..d57fce0daa 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -9,32 +9,44 @@ struct commit;\n struct repository;\n \n struct merge_options {\n+\tstruct repository *repo;\n+\n+\t/* ref names used in console messages and conflict markers */\n \tconst char *ancestor;\n \tconst char *branch1;\n \tconst char *branch2;\n+\n+\t/* rename related options */\n+\tint detect_renames;\n+\tint detect_directory_renames;\n+\tint rename_limit;\n+\tint rename_score;\n+\tint show_rename_progress;\n+\n+\t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n+\tlong xdl_opts;\n \tenum {\n \t\tMERGE_RECURSIVE_NORMAL = 0,\n \t\tMERGE_RECURSIVE_OURS,\n \t\tMERGE_RECURSIVE_THEIRS\n \t} recursive_variant;\n-\tconst char *subtree_shift;\n+\n+\t/* console output related options */\n+\tint verbosity;\n \tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n+\tstruct strbuf obuf;     /* output buffer */\n+\n+\t/* miscellaneous control options */\n+\tconst char *subtree_shift;\n \tunsigned renormalize : 1;\n-\tlong xdl_opts;\n-\tint verbosity;\n-\tint detect_directory_renames;\n-\tint detect_renames;\n-\tint rename_limit;\n-\tint rename_score;\n-\tint needed_rename_limit;\n-\tint show_rename_progress;\n+\n+\t/* internal fields used by the implementation (do NOT set these) */\n \tint call_depth;\n-\tstruct strbuf obuf;\n+\tint needed_rename_limit;\n \tstruct hashmap current_file_dir_set;\n \tstruct string_list df_conflict_file_set;\n \tstruct unpack_trees_options unpack_opts;\n \tstruct index_state orig_index;\n-\tstruct repository *repo;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.22.0.559.g28a8880890.dirty\n\n"},{"id":"379304","messageId":"xmqqblxioufc.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"Re: [PATCH 00/19] Cleanup merge API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-25T18:12:07Z","receivedAt":"2019-07-25T18:12:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Before writing a replacement merge strategy for recursive, I decided\n> to first cleanup the merge API -- streamlining merge-recursive.h and\n> making it more readable.  It includes some fixes I noticed along the\n> way, and the last two patches were some forgotten changes of mine I\n> rediscovered that had minor textual conflicts before I rebased them on\n> this series.\n>\n>     While there are minor textual and semantic dependencies between\n>     these patches (preventing me from splitting up this series), they\n>     are logically separate and can be reviewed independently.\n\nNice.\n\n> Stuff I'd most welcome review on:\n>   * Is cache-tree.c the right place for write_tree_from_memory()?\n>     [see patch 7]  Should there be docs on how it differs from\n>     write_index_as_tree(), already found in cache-tree?  What does\n>     the latter even do?\n\nwrite_index_as_tree() is supposed to write the contents of an index\nstate as a tree object, and return the object ID for the resulting\ntree.  It is the primary interface designed to be used by\nwrite-tree.\n\n> Stuff I didn't address:\n>   * All current callers (3 of them?) of merge_recursive() always pass\n>     ... I'm guessing the first did in an attempt to\n>     exactly match the git-merge-recursive.py scripts' behavior.\n\nThat matches my recollection.  \n"},{"id":"379308","messageId":"CABPp-BGkGch-ebOrJTprw3oGgubgZyyVBCCM30VLbC24BU+MUQ@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqblxioufc.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/19] Cleanup merge API","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T19:06:25Z","receivedAt":"2019-07-25T19:06:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 25, 2019 at 11:12 AM Junio C Hamano <gitster@pobox.com> wrote:\n\n> > Stuff I'd most welcome review on:\n> >   * Is cache-tree.c the right place for write_tree_from_memory()?\n> >     [see patch 7]  Should there be docs on how it differs from\n> >     write_index_as_tree(), already found in cache-tree?  What does\n> >     the latter even do?\n>\n> write_index_as_tree() is supposed to write the contents of an index\n> state as a tree object, and return the object ID for the resulting\n> tree.  It is the primary interface designed to be used by\n> write-tree.\n\nOther than the last sentence, that also sounds like the description of\nwrite_index_as_tree() -- at least as best I understood it.  But I\ndon't understand enough of the cache-tree and locking code to\ndetermine if these are accidental duplicates of the same functionality\n(meaning we could delete one, or rewrite one in terms of the other),\nor whether there's a useful distinction between the two.  I guess it\ndoesn't hurt for now to have both, but I was curious if perhaps one\nwas more robust, and wanted to at least flag it for someone to take a\nlook.\n"},{"id":"379309","messageId":"nycvar.QRO.7.76.6.1907252055500.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"Re: [PATCH 00/19] Cleanup merge API","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T19:15:08Z","receivedAt":"2019-07-25T19:15:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n>   * All current callers (3 of them?) of merge_recursive() always pass\n>     it a specially created reversed-list for the merge_bases.  Some\n>     history spelunking provides no details on any of these about why;\n>     it appears that the 2nd and 3rd callers reversed the list because\n>     the first did, and I'm guessing the first did in an attempt to\n>     exactly match the git-merge-recursive.py scripts' behavior.\n\nThat is part of the reason.\n\nAfter I worked on converting that Python script with Alex Riesen, I\ntested it on all merge commits I could find at that point in linux.git,\nto compare the outcomes between scripted and non-scripted versions. IIRC\nI even reported those findings back to the mailing list.\n\n*clickety-click*\n\nAh, I did the extensive test on git.git first [*1*]:\nhttps://public-inbox.org/git/Pine.LNX.4.63.0608092232000.13885@wbgn013.biozentrum.uni-wuerzburg.de/\n\nAs you can see, it seemed that the reverse order of merge bases (trying\nto merge the oldest two merge bases first, then merging with the\n3rd-oldest, etc) avoided more merge conflicts than the original order.\n\n>     But if the API needs them in a reverse order from what people\n>     would normally expect to pass them in, shouldn't it reverse them\n>     itself instead of making all callers do it?\n\nI would not, as the order the merge bases are passed in defines the\norder in which they are handled.\n\n>     Also, the order shouldn't matter when there are no conflicts, and\n>     when there are conflicts it'd only change which side of the\n>     conflict markers the made-up virtual merge base would list things\n>     in.\n\nThat's what I thought, right up until I re-created those merge commits.\n\nI really forgot most of the details, but I seem to remember that there\nwas a puzzling one where the reverse order caused no merge conflicts,\nand the original order caused a double merge conflict.\n\n>     However, we do have tests with recursive virtual merge bases\n>     and which test the output, and I didn't want to try to clean those\n>     all up.  Besides, the current order shows nicely when commits are\n>     named things like \"L1\", \"L2\", \"R1\", \"R2\" -- it's nice having a\n>     well defined left and right side.  Wasn't sure what to do yet, so\n>     I just punted for now; this series is already long enough...\n\nGood. I think you would have entered an unpleasant space if you had run\ndown that rabbit hole ;-)\n\nCiao,\nDscho\n\nFootnote *1*: I did remember correctly, though, that I re-created all\nmerge commits of then-current linux.git:\nhttps://public-inbox.org/git/Pine.LNX.4.63.0608131550290.10541@wbgn013.biozentrum.uni-wuerzburg.de/\n"},{"id":"379310","messageId":"nycvar.QRO.7.76.6.1907252120300.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-4-newren@gmail.com","subject":"Re: [PATCH 03/19] Ensure index matches head before invoking merge machinery, round N","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T19:41:30Z","receivedAt":"2019-07-25T19:41:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> This is the bug that just won't die; there always seems to be another\n> form of it somewhere.  See the commit message of 55f39cf7551b (\"merge:\n> fix misleading pre-merge check documentation\", 2018-06-30) for a more\n> detailed explanation), but in short:\n>\n> <quick summary>\n>\n> builtin/merge.c contains this important requirement for merge\n> strategies:\n>\n>     ...the index must be in sync with the head commit.  The strategies are\n>     responsible to ensure this.\n>\n> This condition is important to enforce because there are two likely\n> failure cases when the index isn't in sync with the head commit:\n>\n>   * we silently throw away changes the user had staged before the merge\n>\n>   * we accidentally (and silently) include changes in the merge that\n>     were not part of either of the branches/trees being merged\n>\n> Discarding users' work and mis-merging are both bad outcomes, especially\n> when done silently, so naturally this rule was stated sternly -- but,\n> unfortunately totally ignored in practice unless and until actual bugs\n> were found.  But, fear not: the bugs from this were fixed in commit\n>   ee6566e8d70d (\"[PATCH] Rewrite read-tree\", 2005-09-05)\n> through a rewrite of read-tree (again, commit 55f39cf7551b has a more\n> detailed explanation of how this affected merge).  And it was fixed\n> again in commit\n>   160252f81626 (\"git-merge-ours: make sure our index matches HEAD\", 2005-11-03)\n> ...and it was fixed again in commit\n>   3ec62ad9ffba (\"merge-octopus: abort if index does not match HEAD\", 2016-04-09)\n> ...and again in commit\n>   65170c07d466 (\"merge-recursive: avoid incorporating uncommitted changes in a merge\", 2017-12-21)\n> ...and again in commit\n>   eddd1a411d93 (\"merge-recursive: enforce rule that index matches head before merging\", 2018-06-30)\n>\n> ...with multiple testcases added to the testsuite that could be\n> enumerated in even more commits.\n>\n> Then, finally, in a patch in the same series as the last fix above, the\n> documentation about this requirement was fixed in commit 55f39cf7551b\n> (\"merge: fix misleading pre-merge check documentation\", 2018-06-30), and\n> we all lived happily ever after...\n>\n> </quick summary>\n\nWhoa. What a story.\n\n> Unfortunately, \"ever after\" apparently denotes a limited time and it\n> expired today.  The merge-recursive rule to enforce that index matches\n> head was at the beginning of merge_trees() and would only trigger when\n> opt->call_depth was 0.  Since merge_recursive() doesn't call\n> merge_trees() until after returning from recursing, this meant that the\n> check wasn't triggered by merge_recursive() until it had first finished\n> all the intermediate merges to create virtual merge bases.  That is a\n> potentially HUGE amount of computation (and writing of intermediate\n> merge results into the .git/objects directory) before it errors out and\n> says, in effect, \"Sorry, I can't do any merging because you have some\n> local changes that would be overwritten.\"\n>\n> Trying to enforce that all of merge_trees(), merge_recursive(), and\n> merge_recursive_generic() checked the index == head condition earlier\n> resulted in a bunch of broken tests.  It turns out that\n> merge_recursive() has code to drop and reload the cache while recursing\n> to create intermediate virtual merge bases, but unfortunately that code\n> runs even when no recursion is necessary.  This unconditional dropping\n> and reloading of the cache masked a few bugs:\n>\n>   * builtin/merge-recursive.c: didn't even bother loading the index.\n>\n>   * builtin/stash.c: feels like a fake 'builtin' because it repeatedly\n>     invokes git subprocesses all over the place, mixed with other\n>     operations.  In particular, invoking \"git reset\" will reset the\n>     index on disk, but the parent process that invoked it won't\n>     automatically have its in-memory index updated.\n\nYep, the idea was to move fast to a built-in, and then continue by\nconverting all those process spawns to proper calls to libgit \"API\"\nfunctions.\n\nSadly, that did not happen yet.\n\nAnd you're absolutely right that failing to re-read the index after\nspawning a `reset --hard` causes problems. IIRC I fixed them all,\nthough, see\nhttps://public-inbox.org/git/nycvar.QRO.7.76.6.1902191127420.41@tvgsbejvaqbjf.bet/\n\n> So, load the index in builtin/merge-recursive.c, reload the in-memory\n> index in builtin/stash.c, and modify the t3030 testcase to correctly\n> setup the index and make sure that the test fails in the expected way\n> (meaning it reports a rename/rename conflict).\n\nMakes sense to me.\n\n> diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\n> index 5b910e351e..a4bfd8fc51 100644\n> --- a/builtin/merge-recursive.c\n> +++ b/builtin/merge-recursive.c\n> @@ -1,3 +1,4 @@\n> +#include \"cache.h\"\n>  #include \"builtin.h\"\n>  #include \"commit.h\"\n>  #include \"tag.h\"\n> @@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n>  \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n>  \t\tdie(_(\"not handling anything other than two heads merge.\"));\n>\n> +\tif (repo_read_index_unmerged(the_repository))\n> +\t\tdie_resolve_conflict(\"merge\");\n\nFor a moment I was unsure whether `_unmerged()` is the right thing to do\nhere, as it specifically allows to read the index even when there are\nconflict stages. But I guess it does not matter too much here. I\nprobably would have opted for `repo_read_index()` instead, though.\n\n> +\n>  \to.branch1 = argv[++i];\n>  \to.branch2 = argv[++i];\n>\n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index fde6397caa..bec011c1bb 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n>  \t\t\t\treturn error(_(\"could not save index tree\"));\n>\n>  \t\t\treset_head();\n> +\t\t\tdiscard_cache();\n> +\t\t\tread_cache();\n\nI was honestly puzzled why this is necessary, at first. The preceding\ncontext expands to this:\n\n                        discard_cache();\n                        read_cache();\n                        if (write_cache_as_tree(&index_tree, 0, NULL))\n                                return error(_(\"could not save index tree\"));\n\nSo basically, we already discard the index, read it again, then write it\nas a tree, then reset and then we have to discard the index again?\n\nBut of course, if there are uncommitted changes, this would write a tree\ndifferent from HEAD, then reset the index to match HEAD, so indeed, this\ndiscard/read dance is necessary.\n\nSo this hunk is good.\n\n>  \t\t}\n>  \t}\n>\n> diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\n> index ff641b348a..a37bcc58a0 100755\n> --- a/t/t3030-merge-recursive.sh\n> +++ b/t/t3030-merge-recursive.sh\n> @@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '\n>  test_expect_success 'merge-recursive remembers the names of all base trees' '\n>  \tgit reset --hard HEAD &&\n>\n> +\t# make the index match $c1 so that merge-recursive below does not\n> +\t# fail early\n> +\tgit diff --binary HEAD $c1 -- | git apply --cached &&\n> +\n>  \t# more trees than static slots used by oid_to_hex()\n>  \tfor commit in $c0 $c2 $c4 $c5 $c6 $c7\n>  \tdo\n>  \t\tgit rev-parse \"$commit^{tree}\"\n>  \tdone >trees &&\n>\n> -\t# ignore the return code -- it only fails because the input is weird\n> +\t# ignore the return code; it only fails because the input is weird...\n>  \ttest_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&\n>\n> +\t# ...but make sure it fails in the expected way\n> +\ttest_i18ngrep CONFLICT.*rename/rename out &&\n> +\n\nThis is obviously a good change: it strengthens the test case by fixing\na subtle bug.\n\nThanks,\nDscho\n\n>  \t# merge-recursive prints in reverse order, but we do not care\n>  \tsort <trees >expect &&\n>  \tsed -n \"s/^virtual //p\" out | sort >actual &&\n> --\n> 2.22.0.559.g28a8880890.dirty\n>\n>\n"},{"id":"379311","messageId":"nycvar.QRO.7.76.6.1907252142470.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-5-newren@gmail.com","subject":"Re: [PATCH 04/19] merge-recursive: exit early if index != head","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T19:51:00Z","receivedAt":"2019-07-25T19:51:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> We had a rule to enforce that the index matches head, but it was found\n> at the beginning of merge_trees() and would only trigger when\n> opt->call_depth was 0.  Since merge_recursive() doesn't call\n> merge_trees() until after returning from recursing, this meant that the\n> check wasn't triggered by merge_recursive() until it had first finished\n> all the intermediate merges to create virtual merge bases.  That is a\n> potentially huge amount of computation (and writing of intermediate\n> merge results into the .git/objects directory) before it errors out and\n> says, in effect, \"Sorry, I can't do any merging because you have some\n> local changes that would be overwritten.\"\n>\n> Further, not enforcing this requirement earlier allowed other bugs (such\n> as an unintentional unconditional dropping and reloading of the index in\n> merge_recursive() even when no recursion was necessary), to mask bugs in\n> other callers (which were fixed in the commit prior to this one).\n>\n> Make sure we do the index == head check at the beginning of the merge,\n> and error out immediately if it fails.\n\nVery clear commit message.\n\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 37bb94fb4d..b762ecd7bd 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3381,21 +3381,14 @@ static int process_entry(struct merge_options *opt,\n>  \treturn clean_merge;\n>  }\n>\n> -int merge_trees(struct merge_options *opt,\n> -\t\tstruct tree *head,\n> -\t\tstruct tree *merge,\n> -\t\tstruct tree *common,\n> -\t\tstruct tree **result)\n> +static int merge_trees_internal(struct merge_options *opt,\n\nIn other, similar cases, we seem to use the suffix `_1`. Not sure\nwhether you want to change that here.\n\n> +\t\t\t\tstruct tree *head,\n> +\t\t\t\tstruct tree *merge,\n> +\t\t\t\tstruct tree *common,\n> +\t\t\t\tstruct tree **result)\n>  {\n>  \tstruct index_state *istate = opt->repo->index;\n>  \tint code, clean;\n> -\tstruct strbuf sb = STRBUF_INIT;\n> -\n> -\tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n> -\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n> -\t\t    sb.buf);\n> -\t\treturn -1;\n> -\t}\n>\n>  \tif (opt->subtree_shift) {\n>  \t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n> @@ -3499,11 +3492,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n>   * Merge the commits h1 and h2, return the resulting virtual\n>   * commit object and a flag indicating the cleanness of the merge.\n>   */\n> -int merge_recursive(struct merge_options *opt,\n> -\t\t    struct commit *h1,\n> -\t\t    struct commit *h2,\n> -\t\t    struct commit_list *ca,\n> -\t\t    struct commit **result)\n> +static int merge_recursive_internal(struct merge_options *opt,\n\nSame here.\n\n> +\t\t\t\t    struct commit *h1,\n> +\t\t\t\t    struct commit *h2,\n> +\t\t\t\t    struct commit_list *ca,\n> +\t\t\t\t    struct commit **result)\n>  {\n>  \tstruct commit_list *iter;\n>  \tstruct commit *merged_common_ancestors;\n> [...]\n> @@ -3596,6 +3589,58 @@ int merge_recursive(struct merge_options *opt,\n>  \treturn clean;\n>  }\n>\n> +static int merge_start(struct merge_options *opt, struct tree *head)\n\nI would prefer to call this something like `check_invariants()` or\nsomething similar.\n\n> +{\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\n> +\tassert(opt->branch1 && opt->branch2);\n> +\n> +\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n> +\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n> +\t\t    sb.buf);\n\nI know that you did not introduce this leak, but maybe we could slip an\n`strbuf_release(&sb);` in at this point?\n\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static void merge_finalize(struct merge_options *opt)\n> +{\n> +\t/* Common code for wrapping up merges will be added here later */\n> +}\n\nI was about to comment that this complicates this here diff and that we\nshould do this when we need it, but I just peeked into the next patch,\nand it uses it, so I leave this here paragraph only to show that I\nactually reviewed this part, too.\n\nAnd of course, if we have a `merge_finalize()`, then a `merge_start()`\ndoes not sound all that bad, either.\n\nThe rest looks good to me.\n\nThanks,\nDscho\n\n> +\n> +int merge_trees(struct merge_options *opt,\n> +\t\tstruct tree *head,\n> +\t\tstruct tree *merge,\n> +\t\tstruct tree *common,\n> +\t\tstruct tree **result)\n> +{\n> +\tint ret;\n> +\n> +\tif (merge_start(opt, head))\n> +\t\treturn -1;\n> +\tret = merge_trees_internal(opt, head, merge, common, result);\n> +\tmerge_finalize(opt);\n> +\n> +\treturn ret;\n> +}\n> +\n> +int merge_recursive(struct merge_options *opt,\n> +\t\t    struct commit *h1,\n> +\t\t    struct commit *h2,\n> +\t\t    struct commit_list *ca,\n> +\t\t    struct commit **result)\n> +{\n> +\tint ret;\n> +\n> +\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n> +\t\treturn -1;\n> +\tret = merge_recursive_internal(opt, h1, h2, ca, result);\n> +\tmerge_finalize(opt);\n> +\n> +\treturn ret;\n> +}\n> +\n>  static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n>  \t\t\t      const char *name)\n>  {\n> --\n> 2.22.0.559.g28a8880890.dirty\n>\n>\n"},{"id":"379312","messageId":"nycvar.QRO.7.76.6.1907252153530.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-7-newren@gmail.com","subject":"Re: [PATCH 06/19] Change call signature of write_tree_from_memory()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T19:55:19Z","receivedAt":"2019-07-25T19:55:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 7f56cb0ed1..1a3c6ab7f3 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> [...]\n> @@ -434,11 +434,10 @@ struct tree *write_tree_from_memory(struct merge_options *opt)\n>\n>  \tif (!cache_tree_fully_valid(istate->cache_tree) &&\n>  \t    cache_tree_update(istate, 0) < 0) {\n> -\t\terr(opt, _(\"error building trees\"));\n> -\t\treturn NULL;\n> +\t\tBUG(\"error building trees\");\n\nHmm. Is it possible that something else than a bug in Git causes this to\nfail?\n\nI wonder, for example, whether a full disk can cause\n`cache_tree_update()` to return a negative value.\n\nCiao,\nDscho\n"},{"id":"379313","messageId":"CABPp-BHahUojy1+cBrOTCvg-w3n-xJp3yE3Br6v8jMwnMzQwCQ@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1907252120300.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 03/19] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T19:58:35Z","receivedAt":"2019-07-25T19:58:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Thu, Jul 25, 2019 at 12:41 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 25 Jul 2019, Elijah Newren wrote:\n>\n<snip>\n> > ...And it was fixed again in commit\n> >   160252f81626 (\"git-merge-ours: make sure our index matches HEAD\", 2005-11-03)\n> > ...and it was fixed again in commit\n> >   3ec62ad9ffba (\"merge-octopus: abort if index does not match HEAD\", 2016-04-09)\n> > ...and again in commit\n> >   65170c07d466 (\"merge-recursive: avoid incorporating uncommitted changes in a merge\", 2017-12-21)\n> > ...and again in commit\n> >   eddd1a411d93 (\"merge-recursive: enforce rule that index matches head before merging\", 2018-06-30)\n> >\n> > ...with multiple testcases added to the testsuite that could be\n> > enumerated in even more commits.\n> >\n> > Then, finally, in a patch in the same series as the last fix above, the\n> > documentation about this requirement was fixed in commit 55f39cf7551b\n> > (\"merge: fix misleading pre-merge check documentation\", 2018-06-30), and\n> > we all lived happily ever after...\n> >\n> > </quick summary>\n>\n> Whoa. What a story.\n\nI know, right?\n\n> > diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\n> > index 5b910e351e..a4bfd8fc51 100644\n> > --- a/builtin/merge-recursive.c\n> > +++ b/builtin/merge-recursive.c\n> > @@ -1,3 +1,4 @@\n> > +#include \"cache.h\"\n> >  #include \"builtin.h\"\n> >  #include \"commit.h\"\n> >  #include \"tag.h\"\n> > @@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n> >       if (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n> >               die(_(\"not handling anything other than two heads merge.\"));\n> >\n> > +     if (repo_read_index_unmerged(the_repository))\n> > +             die_resolve_conflict(\"merge\");\n>\n> For a moment I was unsure whether `_unmerged()` is the right thing to do\n> here, as it specifically allows to read the index even when there are\n> conflict stages. But I guess it does not matter too much here. I\n> probably would have opted for `repo_read_index()` instead, though.\n\nThe names repo_read_index() and repo_read_index_unmerged() actually\nseem slightly misleading to me; they seem to do the opposite of what\nyou'd think they do.\n\nrepo_read_index() reads in an index and allows unmerged entries and\nreturns istate->cache_nr.\n\nrepo_read_index_unmerged() calls repo_read_index(), then checks to see\nif any of the entries are unmerged and returns whether or not any\nunmerged entries were found.\n\nSo, the way to disallow conflict stages isn't to use\nrepo_read_index(), but to use repo_read_index_unmerged(), as I did.\nCounter-intuitive, I know.\n\n<snip>\n\n> But of course, if there are uncommitted changes, this would write a tree\n> different from HEAD, then reset the index to match HEAD, so indeed, this\n> discard/read dance is necessary.\n>\n> So this hunk is good.\n>\n<snip>\n>\n> This is obviously a good change: it strengthens the test case by fixing\n> a subtle bug.\n>\n> Thanks,\n> Dscho\n\n\nThanks for taking a look!\n"},{"id":"379314","messageId":"nycvar.QRO.7.76.6.1907252159250.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-11-newren@gmail.com","subject":"Re: [PATCH 10/19] merge-recursive: rename 'mrtree' to 'result_tree', for clarity","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T20:02:46Z","receivedAt":"2019-07-25T20:02:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> It is not at all clear what 'mr' was supposed to stand for, at least not\n> to me.  Pick a clearer name for this variable.\n\nI got curious, and it looks as if I introduced this in 3af244caa82\n(Cumulative update of merge-recursive in C, 2006-07-27), where I moved\nthe variable of type `struct tree` out of a `struct merge_tree_result`\n(probably because I found the latter a bit pointless).\n\nThe best backsplanation I have for the \"mr\" is therefore \"merge result\".\n\nI like `result_tree` better.\n\nThanks,\nDscho\n\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 7 ++++---\n>  1 file changed, 4 insertions(+), 3 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 0a90546824..61faa26c4f 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3475,7 +3475,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  {\n>  \tstruct commit_list *iter;\n>  \tstruct commit *merged_merge_bases;\n> -\tstruct tree *mrtree;\n> +\tstruct tree *result_tree;\n>  \tint clean;\n>\n>  \tif (show(opt, 4)) {\n> @@ -3545,14 +3545,15 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n>  \t\t\t\t     repo_get_commit_tree(opt->repo,\n>  \t\t\t\t\t\t\t  merged_merge_bases),\n> -\t\t\t\t     &mrtree);\n> +\t\t\t\t     &result_tree);\n>  \tif (clean < 0) {\n>  \t\tflush_output(opt);\n>  \t\treturn clean;\n>  \t}\n>\n>  \tif (opt->call_depth) {\n> -\t\t*result = make_virtual_commit(opt->repo, mrtree, \"merged tree\");\n> +\t\t*result = make_virtual_commit(opt->repo, result_tree,\n> +\t\t\t\t\t      \"merged tree\");\n>  \t\tcommit_list_insert(h1, &(*result)->parents);\n>  \t\tcommit_list_insert(h2, &(*result)->parents->next);\n>  \t}\n> --\n> 2.22.0.559.g28a8880890.dirty\n>\n>\n"},{"id":"379315","messageId":"CABPp-BEfqsCn11kHEAdHX9+fFv6iiSLV9TTL5XaZ_aDccNft6w@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1907252142470.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 04/19] merge-recursive: exit early if index != head","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T20:12:00Z","receivedAt":"2019-07-25T20:12:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 25, 2019 at 12:51 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 25 Jul 2019, Elijah Newren wrote:\n>\n> > We had a rule to enforce that the index matches head, but it was found\n> > at the beginning of merge_trees() and would only trigger when\n> > opt->call_depth was 0.  Since merge_recursive() doesn't call\n> > merge_trees() until after returning from recursing, this meant that the\n> > check wasn't triggered by merge_recursive() until it had first finished\n> > all the intermediate merges to create virtual merge bases.  That is a\n> > potentially huge amount of computation (and writing of intermediate\n> > merge results into the .git/objects directory) before it errors out and\n> > says, in effect, \"Sorry, I can't do any merging because you have some\n> > local changes that would be overwritten.\"\n> >\n> > Further, not enforcing this requirement earlier allowed other bugs (such\n> > as an unintentional unconditional dropping and reloading of the index in\n> > merge_recursive() even when no recursion was necessary), to mask bugs in\n> > other callers (which were fixed in the commit prior to this one).\n> >\n> > Make sure we do the index == head check at the beginning of the merge,\n> > and error out immediately if it fails.\n>\n> Very clear commit message.\n>\n> > diff --git a/merge-recursive.c b/merge-recursive.c\n> > index 37bb94fb4d..b762ecd7bd 100644\n> > --- a/merge-recursive.c\n> > +++ b/merge-recursive.c\n> > @@ -3381,21 +3381,14 @@ static int process_entry(struct merge_options *opt,\n> >       return clean_merge;\n> >  }\n> >\n> > -int merge_trees(struct merge_options *opt,\n> > -             struct tree *head,\n> > -             struct tree *merge,\n> > -             struct tree *common,\n> > -             struct tree **result)\n> > +static int merge_trees_internal(struct merge_options *opt,\n>\n> In other, similar cases, we seem to use the suffix `_1`. Not sure\n> whether you want to change that here.\n\nHmm, we do seem to use `_1` about 2.5 times as frequently as\n`_internal`, but both do seem to be in use to me (e.g.\ninit_tree_desc_internal, convert_to_working_tree_internal,\nrepo_parse_commit_internal).  `_1` does have the advantage of being\nshorter, which makes lines fit in 80 columns better, but `_internal`\nseems like a clearer description to me.\n\nSo...I'm not sure what's best here.  I don't have a strong opinion,\nbut I'm inclined towards laziness and leaving it alone unless someone\nelse has strong opinions.\n\n> > +{\n> > +     struct strbuf sb = STRBUF_INIT;\n> > +\n> > +     assert(opt->branch1 && opt->branch2);\n> > +\n> > +     if (repo_index_has_changes(opt->repo, head, &sb)) {\n> > +             err(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n> > +                 sb.buf);\n>\n> I know that you did not introduce this leak, but maybe we could slip an\n> `strbuf_release(&sb);` in at this point?\n\nOoh, good point.  I'll fix that up for round 2.\n\n> > +             return -1;\n> > +     }\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +static void merge_finalize(struct merge_options *opt)\n> > +{\n> > +     /* Common code for wrapping up merges will be added here later */\n> > +}\n>\n> I was about to comment that this complicates this here diff and that we\n> should do this when we need it, but I just peeked into the next patch,\n> and it uses it, so I leave this here paragraph only to show that I\n> actually reviewed this part, too.\n>\n> And of course, if we have a `merge_finalize()`, then a `merge_start()`\n> does not sound all that bad, either.\n>\n> The rest looks good to me.\n\nThanks!\n"},{"id":"379316","messageId":"nycvar.QRO.7.76.6.1907252206180.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-16-newren@gmail.com","subject":"Re: [PATCH 15/19] merge-recursive: split internal fields into a separate struct","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T20:12:50Z","receivedAt":"2019-07-25T20:13:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> merge_options has several internal fields that should not be set or read\n> by external callers.  This just complicates the API.  Move them into an\n> opaque merge_options_internal struct that is defined only in\n> merge-recursive.c and keep these out of merge-recursive.h.\n\nThis method requires an extra `malloc()`/`free()` pair, leaving room for\nleaks.\n\nI'd rather keep the definition of that struct in `merge-recursive.h`,\nand make the `priv` field a full struct instead of a pointer. That would\nsave on that extra allocation.\n\nCiao,\nDscho\n\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 185 +++++++++++++++++++++++++---------------------\n>  merge-recursive.h |  17 ++---\n>  2 files changed, 106 insertions(+), 96 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index a5049b06a3..c572d37b21 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -29,6 +29,15 @@\n>  #include \"revision.h\"\n>  #include \"commit-reach.h\"\n>\n> +struct merge_options_internal {\n> +\tint call_depth;\n> +\tint needed_rename_limit;\n> +\tstruct hashmap current_file_dir_set;\n> +\tstruct string_list df_conflict_file_set;\n> +\tstruct unpack_trees_options unpack_opts;\n> +\tstruct index_state orig_index;\n> +};\n> +\n>  struct path_hashmap_entry {\n>  \tstruct hashmap_entry e;\n>  \tchar path[FLEX_ARRAY];\n> @@ -309,7 +318,8 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n>\n>  static int show(struct merge_options *opt, int v)\n>  {\n> -\treturn (!opt->call_depth && opt->verbosity >= v) || opt->verbosity >= 5;\n> +\treturn (!opt->priv->call_depth && opt->verbosity >= v) ||\n> +\t\topt->verbosity >= 5;\n>  }\n>\n>  __attribute__((format (printf, 3, 4)))\n> @@ -320,7 +330,7 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)\n>  \tif (!show(opt, v))\n>  \t\treturn;\n>\n> -\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n> +\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n>\n>  \tva_start(ap, fmt);\n>  \tstrbuf_vaddf(&opt->obuf, fmt, ap);\n> @@ -335,7 +345,7 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit\n>  {\n>  \tstruct merge_remote_desc *desc;\n>\n> -\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n> +\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n>  \tdesc = merge_remote_util(commit);\n>  \tif (desc)\n>  \t\tstrbuf_addf(&opt->obuf, \"virtual %s\\n\", desc->name);\n> @@ -403,43 +413,43 @@ static int unpack_trees_start(struct merge_options *opt,\n>  \tstruct tree_desc t[3];\n>  \tstruct index_state tmp_index = { NULL };\n>\n> -\tmemset(&opt->unpack_opts, 0, sizeof(opt->unpack_opts));\n> -\tif (opt->call_depth)\n> -\t\topt->unpack_opts.index_only = 1;\n> +\tmemset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));\n> +\tif (opt->priv->call_depth)\n> +\t\topt->priv->unpack_opts.index_only = 1;\n>  \telse\n> -\t\topt->unpack_opts.update = 1;\n> -\topt->unpack_opts.merge = 1;\n> -\topt->unpack_opts.head_idx = 2;\n> -\topt->unpack_opts.fn = threeway_merge;\n> -\topt->unpack_opts.src_index = opt->repo->index;\n> -\topt->unpack_opts.dst_index = &tmp_index;\n> -\topt->unpack_opts.aggressive = !merge_detect_rename(opt);\n> -\tsetup_unpack_trees_porcelain(&opt->unpack_opts, \"merge\");\n> +\t\topt->priv->unpack_opts.update = 1;\n> +\topt->priv->unpack_opts.merge = 1;\n> +\topt->priv->unpack_opts.head_idx = 2;\n> +\topt->priv->unpack_opts.fn = threeway_merge;\n> +\topt->priv->unpack_opts.src_index = opt->repo->index;\n> +\topt->priv->unpack_opts.dst_index = &tmp_index;\n> +\topt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);\n> +\tsetup_unpack_trees_porcelain(&opt->priv->unpack_opts, \"merge\");\n>\n>  \tinit_tree_desc_from_tree(t+0, common);\n>  \tinit_tree_desc_from_tree(t+1, head);\n>  \tinit_tree_desc_from_tree(t+2, merge);\n>\n> -\trc = unpack_trees(3, t, &opt->unpack_opts);\n> +\trc = unpack_trees(3, t, &opt->priv->unpack_opts);\n>  \tcache_tree_free(&opt->repo->index->cache_tree);\n>\n>  \t/*\n> -\t * Update opt->repo->index to match the new results, AFTER saving a copy\n> -\t * in opt->orig_index.  Update src_index to point to the saved copy.\n> -\t * (verify_uptodate() checks src_index, and the original index is\n> -\t * the one that had the necessary modification timestamps.)\n> +\t * Update opt->repo->index to match the new results, AFTER saving a\n> +\t * copy in opt->priv->orig_index.  Update src_index to point to the\n> +\t * saved copy.  (verify_uptodate() checks src_index, and the original\n> +\t * index is the one that had the necessary modification timestamps.)\n>  \t */\n> -\topt->orig_index = *opt->repo->index;\n> +\topt->priv->orig_index = *opt->repo->index;\n>  \t*opt->repo->index = tmp_index;\n> -\topt->unpack_opts.src_index = &opt->orig_index;\n> +\topt->priv->unpack_opts.src_index = &opt->priv->orig_index;\n>\n>  \treturn rc;\n>  }\n>\n>  static void unpack_trees_finish(struct merge_options *opt)\n>  {\n> -\tdiscard_index(&opt->orig_index);\n> -\tclear_unpack_trees_porcelain(&opt->unpack_opts);\n> +\tdiscard_index(&opt->priv->orig_index);\n> +\tclear_unpack_trees_porcelain(&opt->priv->unpack_opts);\n>  }\n>\n>  static int save_files_dirs(const struct object_id *oid,\n> @@ -454,7 +464,7 @@ static int save_files_dirs(const struct object_id *oid,\n>\n>  \tFLEX_ALLOC_MEM(entry, path, base->buf, base->len);\n>  \thashmap_entry_init(entry, path_hash(entry->path));\n> -\thashmap_add(&opt->current_file_dir_set, entry);\n> +\thashmap_add(&opt->priv->current_file_dir_set, entry);\n>\n>  \tstrbuf_setlen(base, baselen);\n>  \treturn (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);\n> @@ -585,7 +595,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n>  \t * If we're merging merge-bases, we don't want to bother with\n>  \t * any working directory changes.\n>  \t */\n> -\tif (opt->call_depth)\n> +\tif (opt->priv->call_depth)\n>  \t\treturn;\n>\n>  \t/* Ensure D/F conflicts are adjacent in the entries list. */\n> @@ -597,7 +607,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n>  \tdf_sorted_entries.cmp = string_list_df_name_compare;\n>  \tstring_list_sort(&df_sorted_entries);\n>\n> -\tstring_list_clear(&opt->df_conflict_file_set, 1);\n> +\tstring_list_clear(&opt->priv->df_conflict_file_set, 1);\n>  \tfor (i = 0; i < df_sorted_entries.nr; i++) {\n>  \t\tconst char *path = df_sorted_entries.items[i].string;\n>  \t\tint len = strlen(path);\n> @@ -613,7 +623,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n>  \t\t    len > last_len &&\n>  \t\t    memcmp(path, last_file, last_len) == 0 &&\n>  \t\t    path[last_len] == '/') {\n> -\t\t\tstring_list_insert(&opt->df_conflict_file_set, last_file);\n> +\t\t\tstring_list_insert(&opt->priv->df_conflict_file_set, last_file);\n>  \t\t}\n>\n>  \t\t/*\n> @@ -680,8 +690,8 @@ static void update_entry(struct stage_data *entry,\n>  static int remove_file(struct merge_options *opt, int clean,\n>  \t\t       const char *path, int no_wd)\n>  {\n> -\tint update_cache = opt->call_depth || clean;\n> -\tint update_working_directory = !opt->call_depth && !no_wd;\n> +\tint update_cache = opt->priv->call_depth || clean;\n> +\tint update_working_directory = !opt->priv->call_depth && !no_wd;\n>\n>  \tif (update_cache) {\n>  \t\tif (remove_file_from_index(opt->repo->index, path))\n> @@ -724,16 +734,16 @@ static char *unique_path(struct merge_options *opt,\n>  \tadd_flattened_path(&newpath, branch);\n>\n>  \tbase_len = newpath.len;\n> -\twhile (hashmap_get_from_hash(&opt->current_file_dir_set,\n> +\twhile (hashmap_get_from_hash(&opt->priv->current_file_dir_set,\n>  \t\t\t\t     path_hash(newpath.buf), newpath.buf) ||\n> -\t       (!opt->call_depth && file_exists(newpath.buf))) {\n> +\t       (!opt->priv->call_depth && file_exists(newpath.buf))) {\n>  \t\tstrbuf_setlen(&newpath, base_len);\n>  \t\tstrbuf_addf(&newpath, \"_%d\", suffix++);\n>  \t}\n>\n>  \tFLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);\n>  \thashmap_entry_init(entry, path_hash(entry->path));\n> -\thashmap_add(&opt->current_file_dir_set, entry);\n> +\thashmap_add(&opt->priv->current_file_dir_set, entry);\n>  \treturn strbuf_detach(&newpath, NULL);\n>  }\n>\n> @@ -775,7 +785,7 @@ static int dir_in_way(struct index_state *istate, const char *path,\n>  static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n>  \t\t\t\t   const struct diff_filespec *blob)\n>  {\n> -\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n> +\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n>  \tstruct cache_entry *ce;\n>\n>  \tif (0 > pos)\n> @@ -783,7 +793,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n>  \t\treturn 0;\n>\n>  \t/* See if the file we were tracking before matches */\n> -\tce = opt->orig_index.cache[pos];\n> +\tce = opt->priv->orig_index.cache[pos];\n>  \treturn (oid_eq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);\n>  }\n>\n> @@ -792,7 +802,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n>   */\n>  static int was_tracked(struct merge_options *opt, const char *path)\n>  {\n> -\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n> +\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n>\n>  \tif (0 <= pos)\n>  \t\t/* we were tracking this path before the merge */\n> @@ -849,12 +859,12 @@ static int was_dirty(struct merge_options *opt, const char *path)\n>  \tstruct cache_entry *ce;\n>  \tint dirty = 1;\n>\n> -\tif (opt->call_depth || !was_tracked(opt, path))\n> +\tif (opt->priv->call_depth || !was_tracked(opt, path))\n>  \t\treturn !dirty;\n>\n> -\tce = index_file_exists(opt->unpack_opts.src_index,\n> +\tce = index_file_exists(opt->priv->unpack_opts.src_index,\n>  \t\t\t       path, strlen(path), ignore_case);\n> -\tdirty = verify_uptodate(ce, &opt->unpack_opts) != 0;\n> +\tdirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;\n>  \treturn dirty;\n>  }\n>\n> @@ -864,8 +874,8 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n>  \tconst char *msg = _(\"failed to create path '%s'%s\");\n>\n>  \t/* Unlink any D/F conflict files that are in the way */\n> -\tfor (i = 0; i < opt->df_conflict_file_set.nr; i++) {\n> -\t\tconst char *df_path = opt->df_conflict_file_set.items[i].string;\n> +\tfor (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {\n> +\t\tconst char *df_path = opt->priv->df_conflict_file_set.items[i].string;\n>  \t\tsize_t pathlen = strlen(path);\n>  \t\tsize_t df_pathlen = strlen(df_path);\n>  \t\tif (df_pathlen < pathlen &&\n> @@ -875,7 +885,7 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n>  \t\t\t       _(\"Removing %s to make room for subdirectory\\n\"),\n>  \t\t\t       df_path);\n>  \t\t\tunlink(df_path);\n> -\t\t\tunsorted_string_list_delete_item(&opt->df_conflict_file_set,\n> +\t\t\tunsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,\n>  \t\t\t\t\t\t\t i, 0);\n>  \t\t\tbreak;\n>  \t\t}\n> @@ -916,7 +926,7 @@ static int update_file_flags(struct merge_options *opt,\n>  {\n>  \tint ret = 0;\n>\n> -\tif (opt->call_depth)\n> +\tif (opt->priv->call_depth)\n>  \t\tupdate_wd = 0;\n>\n>  \tif (update_wd) {\n> @@ -1001,7 +1011,7 @@ static int update_file(struct merge_options *opt,\n>  \t\t       const char *path)\n>  {\n>  \treturn update_file_flags(opt, contents, path,\n> -\t\t\t\t opt->call_depth || clean, !opt->call_depth);\n> +\t\t\t\t opt->priv->call_depth || clean, !opt->priv->call_depth);\n>  }\n>\n>  /* Low level file merging, update and removal */\n> @@ -1030,7 +1040,7 @@ static int merge_3way(struct merge_options *opt,\n>  \tll_opts.extra_marker_size = extra_marker_size;\n>  \tll_opts.xdl_opts = opt->xdl_opts;\n>\n> -\tif (opt->call_depth) {\n> +\tif (opt->priv->call_depth) {\n>  \t\tll_opts.virtual_ancestor = 1;\n>  \t\tll_opts.variant = 0;\n>  \t} else {\n> @@ -1164,7 +1174,7 @@ static int merge_submodule(struct merge_options *opt,\n>  \tstruct object_array merges;\n>\n>  \tint i;\n> -\tint search = !opt->call_depth;\n> +\tint search = !opt->priv->call_depth;\n>\n>  \t/* store a in result in case we fail */\n>  \toidcpy(result, a);\n> @@ -1385,7 +1395,7 @@ static int handle_rename_via_dir(struct merge_options *opt,\n>  \tint mark_conflicted = (opt->detect_directory_renames == 1);\n>  \tassert(ren->dir_rename_original_dest);\n>\n> -\tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n> +\tif (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) {\n>  \t\tmark_conflicted = 1;\n>  \t\tfile_path = unique_path(opt, dest->path, ren->branch);\n>  \t\toutput(opt, 1, _(\"Error: Refusing to lose untracked file at %s; \"\n> @@ -1428,12 +1438,12 @@ static int handle_change_delete(struct merge_options *opt,\n>  \tconst char *update_path = path;\n>  \tint ret = 0;\n>\n> -\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0) ||\n> -\t    (!opt->call_depth && would_lose_untracked(opt, path))) {\n> +\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) ||\n> +\t    (!opt->priv->call_depth && would_lose_untracked(opt, path))) {\n>  \t\tupdate_path = alt_path = unique_path(opt, path, change_branch);\n>  \t}\n>\n> -\tif (opt->call_depth) {\n> +\tif (opt->priv->call_depth) {\n>  \t\t/*\n>  \t\t * We cannot arbitrarily accept either a_sha or b_sha as\n>  \t\t * correct; since there is no true \"middle point\" between\n> @@ -1508,14 +1518,14 @@ static int handle_rename_delete(struct merge_options *opt,\n>  \t\t\t\t     opt->branch2 : opt->branch1);\n>\n>  \tif (handle_change_delete(opt,\n> -\t\t\t\t opt->call_depth ? orig->path : dest->path,\n> -\t\t\t\t opt->call_depth ? NULL : orig->path,\n> +\t\t\t\t opt->priv->call_depth ? orig->path : dest->path,\n> +\t\t\t\t opt->priv->call_depth ? NULL : orig->path,\n>  \t\t\t\t orig, dest,\n>  \t\t\t\t rename_branch, delete_branch,\n>  \t\t\t\t _(\"rename\"), _(\"renamed\")))\n>  \t\treturn -1;\n>\n> -\tif (opt->call_depth)\n> +\tif (opt->priv->call_depth)\n>  \t\treturn remove_file_from_index(opt->repo->index, dest->path);\n>  \telse\n>  \t\treturn update_stages(opt, dest->path, NULL,\n> @@ -1552,7 +1562,7 @@ static int handle_file_collision(struct merge_options *opt,\n>  \t/*\n>  \t * In the recursive case, we just opt to undo renames\n>  \t */\n> -\tif (opt->call_depth && (prev_path1 || prev_path2)) {\n> +\tif (opt->priv->call_depth && (prev_path1 || prev_path2)) {\n>  \t\t/* Put first file (a->oid, a->mode) in its original spot */\n>  \t\tif (prev_path1) {\n>  \t\t\tif (update_file(opt, 1, a, prev_path1))\n> @@ -1581,10 +1591,10 @@ static int handle_file_collision(struct merge_options *opt,\n>  \t/* Remove rename sources if rename/add or rename/rename(2to1) */\n>  \tif (prev_path1)\n>  \t\tremove_file(opt, 1, prev_path1,\n> -\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path1));\n> +\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path1));\n>  \tif (prev_path2)\n>  \t\tremove_file(opt, 1, prev_path2,\n> -\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path2));\n> +\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path2));\n>\n>  \t/*\n>  \t * Remove the collision path, if it wouldn't cause dirty contents\n> @@ -1626,12 +1636,12 @@ static int handle_file_collision(struct merge_options *opt,\n>  \tnull.mode = 0;\n>\n>  \tif (merge_mode_and_contents(opt, &null, a, b, collide_path,\n> -\t\t\t\t    branch1, branch2, opt->call_depth * 2, &mfi))\n> +\t\t\t\t    branch1, branch2, opt->priv->call_depth * 2, &mfi))\n>  \t\treturn -1;\n>  \tmfi.clean &= !alt_path;\n>  \tif (update_file(opt, mfi.clean, &mfi.blob, update_path))\n>  \t\treturn -1;\n> -\tif (!mfi.clean && !opt->call_depth &&\n> +\tif (!mfi.clean && !opt->priv->call_depth &&\n>  \t    update_stages(opt, collide_path, NULL, a, b))\n>  \t\treturn -1;\n>  \tfree(alt_path);\n> @@ -1671,7 +1681,7 @@ static int handle_rename_add(struct merge_options *opt,\n>  \t\t\t\t    &ci->ren1->src_entry->stages[other_stage],\n>  \t\t\t\t    prev_path_desc,\n>  \t\t\t\t    opt->branch1, opt->branch2,\n> -\t\t\t\t    1 + opt->call_depth * 2, &mfi))\n> +\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi))\n>  \t\treturn -1;\n>  \tfree(prev_path_desc);\n>\n> @@ -1689,7 +1699,7 @@ static char *find_path_for_conflict(struct merge_options *opt,\n>  \t\t\t\t    const char *branch2)\n>  {\n>  \tchar *new_path = NULL;\n> -\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0)) {\n> +\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) {\n>  \t\tnew_path = unique_path(opt, path, branch1);\n>  \t\toutput(opt, 1, _(\"%s is a directory in %s adding \"\n>  \t\t\t       \"as %s instead\"),\n> @@ -1720,17 +1730,17 @@ static int handle_rename_rename_1to2(struct merge_options *opt,\n>  \t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\"),\n>  \t       o->path, a->path, ci->ren1->branch,\n>  \t       o->path, b->path, ci->ren2->branch,\n> -\t       opt->call_depth ? _(\" (left unresolved)\") : \"\");\n> +\t       opt->priv->call_depth ? _(\" (left unresolved)\") : \"\");\n>\n>  \tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n>  \t\t\t    a->path, b->path, o->path);\n>  \tif (merge_mode_and_contents(opt, o, a, b, path_desc,\n>  \t\t\t\t    ci->ren1->branch, ci->ren2->branch,\n> -\t\t\t\t    opt->call_depth * 2, &mfi))\n> +\t\t\t\t    opt->priv->call_depth * 2, &mfi))\n>  \t\treturn -1;\n>  \tfree(path_desc);\n>\n> -\tif (opt->call_depth) {\n> +\tif (opt->priv->call_depth) {\n>  \t\t/*\n>  \t\t * FIXME: For rename/add-source conflicts (if we could detect\n>  \t\t * such), this is wrong.  We should instead find a unique\n> @@ -1845,12 +1855,12 @@ static int handle_rename_rename_2to1(struct merge_options *opt,\n>  \t\t\t\t    &ci->ren1->src_entry->stages[ostage1],\n>  \t\t\t\t    path_side_1_desc,\n>  \t\t\t\t    opt->branch1, opt->branch2,\n> -\t\t\t\t    1 + opt->call_depth * 2, &mfi_c1) ||\n> +\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c1) ||\n>  \t    merge_mode_and_contents(opt, b,\n>  \t\t\t\t    &ci->ren2->src_entry->stages[ostage2],\n>  \t\t\t\t    c2, path_side_2_desc,\n>  \t\t\t\t    opt->branch1, opt->branch2,\n> -\t\t\t\t    1 + opt->call_depth * 2, &mfi_c2))\n> +\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c2))\n>  \t\treturn -1;\n>  \tfree(path_side_1_desc);\n>  \tfree(path_side_2_desc);\n> @@ -1891,8 +1901,8 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n>  \tdiff_setup_done(&opts);\n>  \tdiff_tree_oid(&o_tree->object.oid, &tree->object.oid, \"\", &opts);\n>  \tdiffcore_std(&opts);\n> -\tif (opts.needed_rename_limit > opt->needed_rename_limit)\n> -\t\topt->needed_rename_limit = opts.needed_rename_limit;\n> +\tif (opts.needed_rename_limit > opt->priv->needed_rename_limit)\n> +\t\topt->priv->needed_rename_limit = opts.needed_rename_limit;\n>\n>  \tret = xmalloc(sizeof(*ret));\n>  \t*ret = diff_queued_diff;\n> @@ -3022,13 +3032,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n>  \t\treason = _(\"add/add\");\n>\n>  \tassert(o->path && a->path && b->path);\n> -\tif (ci && dir_in_way(opt->repo->index, path, !opt->call_depth,\n> +\tif (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth,\n>  \t\t\t     S_ISGITLINK(ci->ren1->pair->two->mode)))\n>  \t\tdf_conflict_remains = 1;\n>\n>  \tif (merge_mode_and_contents(opt, o, a, b, path,\n>  \t\t\t\t    opt->branch1, opt->branch2,\n> -\t\t\t\t    opt->call_depth * 2, mfi))\n> +\t\t\t\t    opt->priv->call_depth * 2, mfi))\n>  \t\treturn -1;\n>\n>  \t/*\n> @@ -3044,7 +3054,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n>\n>  \t\toutput(opt, 3, _(\"Skipped %s (merged same as existing)\"), path);\n>  \t\tif (add_cacheinfo(opt, &mfi->blob, path,\n> -\t\t\t\t  0, (!opt->call_depth && !is_dirty), 0))\n> +\t\t\t\t  0, (!opt->priv->call_depth && !is_dirty), 0))\n>  \t\t\treturn -1;\n>  \t\t/*\n>  \t\t * However, add_cacheinfo() will delete the old cache entry\n> @@ -3052,8 +3062,8 @@ static int handle_content_merge(struct merge_file_info *mfi,\n>  \t\t * flag to avoid making the file appear as if it were\n>  \t\t * deleted by the user.\n>  \t\t */\n> -\t\tpos = index_name_pos(&opt->orig_index, path, strlen(path));\n> -\t\tce = opt->orig_index.cache[pos];\n> +\t\tpos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n> +\t\tce = opt->priv->orig_index.cache[pos];\n>  \t\tif (ce_skip_worktree(ce)) {\n>  \t\t\tpos = index_name_pos(opt->repo->index, path, strlen(path));\n>  \t\t\tce = opt->repo->index->cache[pos];\n> @@ -3074,7 +3084,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n>\n>  \tif (df_conflict_remains || is_dirty) {\n>  \t\tchar *new_path;\n> -\t\tif (opt->call_depth) {\n> +\t\tif (opt->priv->call_depth) {\n>  \t\t\tremove_file_from_index(opt->repo->index, path);\n>  \t\t} else {\n>  \t\t\tif (!mfi->clean) {\n> @@ -3332,7 +3342,7 @@ static int process_entry(struct merge_options *opt,\n>  \t\t\tconf = _(\"directory/file\");\n>  \t\t}\n>  \t\tif (dir_in_way(opt->repo->index, path,\n> -\t\t\t       !opt->call_depth && !S_ISGITLINK(a->mode),\n> +\t\t\t       !opt->priv->call_depth && !S_ISGITLINK(a->mode),\n>  \t\t\t       0)) {\n>  \t\t\tchar *new_path = unique_path(opt, path, add_branch);\n>  \t\t\tclean_merge = 0;\n> @@ -3341,7 +3351,7 @@ static int process_entry(struct merge_options *opt,\n>  \t\t\t       conf, path, other_branch, path, new_path);\n>  \t\t\tif (update_file(opt, 0, contents, new_path))\n>  \t\t\t\tclean_merge = -1;\n> -\t\t\telse if (opt->call_depth)\n> +\t\t\telse if (opt->priv->call_depth)\n>  \t\t\t\tremove_file_from_index(opt->repo->index, path);\n>  \t\t\tfree(new_path);\n>  \t\t} else {\n> @@ -3406,7 +3416,7 @@ static int merge_trees_internal(struct merge_options *opt,\n>  \tcode = unpack_trees_start(opt, merge_base, head, merge);\n>\n>  \tif (code != 0) {\n> -\t\tif (show(opt, 4) || opt->call_depth)\n> +\t\tif (show(opt, 4) || opt->priv->call_depth)\n>  \t\t\terr(opt, _(\"merging of trees %s and %s failed\"),\n>  \t\t\t    oid_to_hex(&head->object.oid),\n>  \t\t\t    oid_to_hex(&merge->object.oid));\n> @@ -3425,7 +3435,7 @@ static int merge_trees_internal(struct merge_options *opt,\n>  \t\t * opposed to decaring a local hashmap is for convenience\n>  \t\t * so that we don't have to pass it to around.\n>  \t\t */\n> -\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n> +\t\thashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp,\n>  \t\t\t     NULL, 512);\n>  \t\tget_files_dirs(opt, head);\n>  \t\tget_files_dirs(opt, merge);\n> @@ -3462,7 +3472,7 @@ static int merge_trees_internal(struct merge_options *opt,\n>  \t\tstring_list_clear(entries, 1);\n>  \t\tfree(entries);\n>\n> -\t\thashmap_free(&opt->current_file_dir_set, 1);\n> +\t\thashmap_free(&opt->priv->current_file_dir_set, 1);\n>\n>  \t\tif (clean < 0) {\n>  \t\t\tunpack_trees_finish(opt);\n> @@ -3474,7 +3484,8 @@ static int merge_trees_internal(struct merge_options *opt,\n>\n>  \tunpack_trees_finish(opt);\n>\n> -\tif (opt->call_depth && !(*result = write_tree_from_memory(opt->repo)))\n> +\tif (opt->priv->call_depth &&\n> +\t    !(*result = write_tree_from_memory(opt->repo)))\n>  \t\treturn -1;\n>\n>  \treturn clean;\n> @@ -3538,7 +3549,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n>\n>  \tfor (iter = merge_bases; iter; iter = iter->next) {\n>  \t\tconst char *saved_b1, *saved_b2;\n> -\t\topt->call_depth++;\n> +\t\topt->priv->call_depth++;\n>  \t\t/*\n>  \t\t * When the merge fails, the result contains files\n>  \t\t * with conflict markers. The cleanness flag is\n> @@ -3557,14 +3568,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \t\t\treturn -1;\n>  \t\topt->branch1 = saved_b1;\n>  \t\topt->branch2 = saved_b2;\n> -\t\topt->call_depth--;\n> +\t\topt->priv->call_depth--;\n>\n>  \t\tif (!merged_merge_bases)\n>  \t\t\treturn err(opt, _(\"merge returned no commit\"));\n>  \t}\n>\n>  \tdiscard_index(opt->repo->index);\n> -\tif (!opt->call_depth)\n> +\tif (!opt->priv->call_depth)\n>  \t\trepo_read_index(opt->repo);\n>\n>  \topt->ancestor = \"merged common ancestors\";\n> @@ -3579,14 +3590,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \t\treturn clean;\n>  \t}\n>\n> -\tif (opt->call_depth) {\n> +\tif (opt->priv->call_depth) {\n>  \t\t*result = make_virtual_commit(opt->repo, result_tree,\n>  \t\t\t\t\t      \"merged tree\");\n>  \t\tcommit_list_insert(h1, &(*result)->parents);\n>  \t\tcommit_list_insert(h2, &(*result)->parents->next);\n>  \t}\n>  \tflush_output(opt);\n> -\tif (!opt->call_depth && opt->buffer_output < 2)\n> +\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n>  \t\tstrbuf_release(&opt->obuf);\n>  \treturn clean;\n>  }\n> @@ -3603,6 +3614,8 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n>  \t\treturn -1;\n>  \t}\n>\n> +\topt->priv = xcalloc(1, sizeof(*opt->priv));\n> +\tstring_list_init(&opt->priv->df_conflict_file_set, 1);\n>  \treturn 0;\n>  }\n>\n> @@ -3610,7 +3623,9 @@ static void merge_finalize(struct merge_options *opt)\n>  {\n>  \tif (show(opt, 2))\n>  \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n> -\t\t\t\t       opt->needed_rename_limit, 0);\n> +\t\t\t\t       opt->priv->needed_rename_limit, 0);\n> +\tfree(opt->priv);\n> +\topt->priv = NULL;\n>  }\n>\n>  int merge_trees(struct merge_options *opt,\n> @@ -3748,8 +3763,6 @@ void init_merge_options(struct merge_options *opt,\n>\n>  \topt->renormalize = 0;\n>\n> -\tstring_list_init(&opt->df_conflict_file_set, 1);\n> -\n>  \tmerge_recursive_config(opt);\n>  \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n>  \tif (merge_verbosity)\n> diff --git a/merge-recursive.h b/merge-recursive.h\n> index d57fce0daa..a1bb29dc33 100644\n> --- a/merge-recursive.h\n> +++ b/merge-recursive.h\n> @@ -1,13 +1,15 @@\n>  #ifndef MERGE_RECURSIVE_H\n>  #define MERGE_RECURSIVE_H\n>\n> -#include \"string-list.h\"\n> -#include \"unpack-trees.h\"\n> +#include \"strbuf.h\"\n>\n>  struct commit;\n> -\n> +struct commit_list;\n> +struct object_id;\n>  struct repository;\n> +struct tree;\n>\n> +struct merge_options_internal;\n>  struct merge_options {\n>  \tstruct repository *repo;\n>\n> @@ -40,13 +42,8 @@ struct merge_options {\n>  \tconst char *subtree_shift;\n>  \tunsigned renormalize : 1;\n>\n> -\t/* internal fields used by the implementation (do NOT set these) */\n> -\tint call_depth;\n> -\tint needed_rename_limit;\n> -\tstruct hashmap current_file_dir_set;\n> -\tstruct string_list df_conflict_file_set;\n> -\tstruct unpack_trees_options unpack_opts;\n> -\tstruct index_state orig_index;\n> +\t/* internal fields used by the implementation */\n> +\tstruct merge_options_internal *priv;\n>  };\n>\n>  void init_merge_options(struct merge_options *opt, struct repository *repo);\n> --\n> 2.22.0.559.g28a8880890.dirty\n>\n>\n"},{"id":"379318","messageId":"CABPp-BGpCEZ-40_SsT_vU+hCP=MiKzhky6yBr6G=UMnwLNR2Ow@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1907252206180.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 15/19] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-25T20:27:03Z","receivedAt":"2019-07-25T20:27:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 25, 2019 at 1:12 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 25 Jul 2019, Elijah Newren wrote:\n>\n> > merge_options has several internal fields that should not be set or read\n> > by external callers.  This just complicates the API.  Move them into an\n> > opaque merge_options_internal struct that is defined only in\n> > merge-recursive.c and keep these out of merge-recursive.h.\n>\n> This method requires an extra `malloc()`/`free()` pair, leaving room for\n> leaks.\n>\n> I'd rather keep the definition of that struct in `merge-recursive.h`,\n> and make the `priv` field a full struct instead of a pointer. That would\n> save on that extra allocation.\n\nYes, it has one extra malloc()/free() pair for the entire merge operation.  But:\n  * That's a drop in the bucket compared to the allocations already\nperformed during a merge\n  * The allocation and free happen in merge_start() and\nmerge_finalize().  Not much to keep track of, so not much room for\nleaks.\n  * I want what's currently in merge-recursive.h to be as simple as\npossible; I implemented your suggestion first, but it doesn't simplify\nas much as possible.\n\nBut, more importantly:\n  * I want to write an alternative merge strategy providing drop-in\nreplacement functions for merge_trees(), merge_recursive(), and\nmerge_recursive_generic(). Defining merge_options_internal inside\nmerge-recursive.h would mean that I have to have _exactly_ the same\ninternal options in my implementation of merge-ort.c.  That doesn't\nmake sense.\n\nElijah\n"},{"id":"379319","messageId":"nycvar.QRO.7.76.6.1907252217030.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190725174611.14802-20-newren@gmail.com","subject":"Re: [PATCH 19/19] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T20:28:07Z","receivedAt":"2019-07-25T20:28:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> In commit 7ca56aa07619 (\"merge-recursive: add a label for ancestor\",\n> 2010-03-20), a label was added for the '||||||' line to make it have\n> the more informative heading '|||||| merged common ancestors', with\n> the statement:\n>\n>     It would be nicer to use a more informative label.  Perhaps someone\n>     will provide one some day.\n>\n> This chosen label was perfectly reasonable when recursiveness kicks in,\n> i.e. when there are multiple merge bases.  (I can't think of a better\n> label in such cases.)  But it is actually somewhat misleading when there\n> is a unique merge base or no merge base.  Change this based on the\n> number of merge bases:\n>     >=2: \"merged common ancestors\"\n>     1:   <abbreviated commit hash>\n>     0:   \"<empty tree>\"\n\nNice!\n\n> Also, the code in merge_3way making use of opt->ancestor was overly\n> complex because it tried to handle the case of opt->ancestor being NULL.\n> We always set it first, though, so just add an assert that opt->ancestor\n> is not NULL and simplify the surrounding code.\n>\n> Tests have also been added to check that we get the right ancestor name\n> for each of the three cases.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c                 |  35 ++++--\n>  t/t6036-recursive-corner-cases.sh |   8 +-\n>  t/t6047-diff3-conflict-markers.sh | 191 ++++++++++++++++++++++++++++++\n>  3 files changed, 220 insertions(+), 14 deletions(-)\n>  create mode 100755 t/t6047-diff3-conflict-markers.sh\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 4cd6599296..8ac53cacdf 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -1034,7 +1034,7 @@ static int merge_3way(struct merge_options *opt,\n>  {\n>  \tmmfile_t orig, src1, src2;\n>  \tstruct ll_merge_options ll_opts = {0};\n> -\tchar *base_name, *name1, *name2;\n> +\tchar *base, *name1, *name2;\n\nRenaming this variable at the same time as doing other, more involved\nchanges, made this patch a bit harder to review for me, I must admit.\n\n>  \tint merge_status;\n>\n>  \tll_opts.renormalize = opt->renormalize;\n> @@ -1058,16 +1058,13 @@ static int merge_3way(struct merge_options *opt,\n>  \t\t}\n>  \t}\n>\n> -\tassert(a->path && b->path && o->path);\n> -\tif (strcmp(a->path, b->path) ||\n> -\t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n> -\t\tbase_name = opt->ancestor == NULL ? NULL :\n> -\t\t\tmkpathdup(\"%s:%s\", opt->ancestor, o->path);\n> +\tassert(a->path && b->path && o->path && opt->ancestor);\n> +\tif (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {\n> +\t\tbase  = mkpathdup(\"%s:%s\", opt->ancestor, o->path);\n>  \t\tname1 = mkpathdup(\"%s:%s\", branch1, a->path);\n>  \t\tname2 = mkpathdup(\"%s:%s\", branch2, b->path);\n>  \t} else {\n> -\t\tbase_name = opt->ancestor == NULL ? NULL :\n> -\t\t\tmkpathdup(\"%s\", opt->ancestor);\n> +\t\tbase  = mkpathdup(\"%s\", opt->ancestor);\n>  \t\tname1 = mkpathdup(\"%s\", branch1);\n>  \t\tname2 = mkpathdup(\"%s\", branch2);\n>  \t}\n> @@ -1076,11 +1073,11 @@ static int merge_3way(struct merge_options *opt,\n>  \tread_mmblob(&src1, &a->oid);\n>  \tread_mmblob(&src2, &b->oid);\n>\n> -\tmerge_status = ll_merge(result_buf, a->path, &orig, base_name,\n> +\tmerge_status = ll_merge(result_buf, a->path, &orig, base,\n>  \t\t\t\t&src1, name1, &src2, name2,\n>  \t\t\t\topt->repo->index, &ll_opts);\n>\n> -\tfree(base_name);\n> +\tfree(base);\n>  \tfree(name1);\n>  \tfree(name2);\n>  \tfree(orig.ptr);\n> @@ -3517,6 +3514,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \tstruct commit *merged_merge_bases;\n>  \tstruct tree *result_tree;\n>  \tint clean;\n> +\tint num_merge_bases;\n> +\tstruct strbuf commit_name = STRBUF_INIT;\n>\n>  \tif (show(opt, 4)) {\n>  \t\toutput(opt, 4, _(\"Merging:\"));\n> @@ -3538,6 +3537,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \t\t\toutput_commit_title(opt, iter->item);\n>  \t}\n>\n> +\tnum_merge_bases = commit_list_count(merge_bases);\n\nAt first, I thought that this does not require a separate variable\nbecause it is used exactly once.\n\nBut then I realized that the next line changes the number of commits in\n`merge_bases`, so it actually _is_ required.\n\n>  \tmerged_merge_bases = pop_commit(&merge_bases);\n>  \tif (merged_merge_bases == NULL) {\n>  \t\t/* if there is no common ancestor, use an empty tree */\n> @@ -3579,13 +3579,26 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \tif (!opt->priv->call_depth)\n>  \t\trepo_read_index(opt->repo);\n>\n> -\topt->ancestor = \"merged common ancestors\";\n> +\tswitch (num_merge_bases) {\n> +\tcase 0:\n> +\t\topt->ancestor = \"<empty tree>\";\n> +\t\tbreak;\n> +\tcase 1:\n> +\t\tstrbuf_add_unique_abbrev(&commit_name,\n\nThe name `commit_name` would suggest a different usage to me. How about\n`pretty_merge_base`?\n\n> +\t\t\t\t\t &merged_merge_bases->object.oid,\n> +\t\t\t\t\t DEFAULT_ABBREV);\n> +\t\topt->ancestor = commit_name.buf;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\topt->ancestor = \"merged common ancestors\";\n> +\t}\n>  \tclean = merge_trees_internal(opt,\n>  \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n>  \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n>  \t\t\t\t     repo_get_commit_tree(opt->repo,\n>  \t\t\t\t\t\t\t  merged_merge_bases),\n>  \t\t\t\t     &result_tree);\n> +\tstrbuf_release(&commit_name);\n>  \tif (clean < 0) {\n>  \t\tflush_output(opt);\n>  \t\treturn clean;\n\nI was a bit too tired to look more closely at the test cases, in\nparticular after seeing the enormous complexity of the added test\nscript. I wonder whether it really has to be all that complex (e.g. why\nuse a complicated `test_seq` when a `test_commit A A.t\n\"1${LF}2${LF}3${LF}A${LF}` would suffice? Why start by `git checkout\n--orphan` on a just-created repository?)\n\nBut otherwise, the patch series looks pretty good to me.\n\nThanks,\nDscho\n"},{"id":"379320","messageId":"xmqq7e85pw3y.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"CABPp-BGkGch-ebOrJTprw3oGgubgZyyVBCCM30VLbC24BU+MUQ@mail.gmail.com","subject":"Re: [PATCH 00/19] Cleanup merge API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-25T22:50:25Z","receivedAt":"2019-07-25T22:50:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> On Thu, Jul 25, 2019 at 11:12 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n>> > Stuff I'd most welcome review on:\n>> >   * Is cache-tree.c the right place for write_tree_from_memory()?\n>> >     [see patch 7]  Should there be docs on how it differs from\n>> >     write_index_as_tree(), already found in cache-tree?  What does\n>> >     the latter even do?\n>>\n>> write_index_as_tree() is supposed to write the contents of an index\n>> state as a tree object, and return the object ID for the resulting\n>> tree.  It is the primary interface designed to be used by\n>> write-tree.\n>\n> Other than the last sentence, that also sounds like the description of\n> write_index_as_tree() -- at least as best I understood it.\n\nYes, I didn't even know merge-recursive had its own variant.  I\nsuspect that back when \"merge-recursive in C\" was being developed,\nit first used \"git write-tree\" via run_command(), and then just\ncopied and pasted what was done in the write-tree implementation\nwithout bothering to refactor it into write_index_as_tree() and its\nown bits about the unmerged index.\n\n"},{"id":"379327","messageId":"CABPp-BHs47CQ8S72-Wq5SHr-BsBOA5tUcbTeYQupchz6Xi8T4w@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1907252153530.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 06/19] Change call signature of write_tree_from_memory()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T04:58:43Z","receivedAt":"2019-07-26T04:58:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Thu, Jul 25, 2019 at 12:55 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 25 Jul 2019, Elijah Newren wrote:\n>\n> > diff --git a/merge-recursive.c b/merge-recursive.c\n> > index 7f56cb0ed1..1a3c6ab7f3 100644\n> > --- a/merge-recursive.c\n> > +++ b/merge-recursive.c\n> > [...]\n> > @@ -434,11 +434,10 @@ struct tree *write_tree_from_memory(struct merge_options *opt)\n> >\n> >       if (!cache_tree_fully_valid(istate->cache_tree) &&\n> >           cache_tree_update(istate, 0) < 0) {\n> > -             err(opt, _(\"error building trees\"));\n> > -             return NULL;\n> > +             BUG(\"error building trees\");\n>\n> Hmm. Is it possible that something else than a bug in Git causes this to\n> fail?\n>\n> I wonder, for example, whether a full disk can cause\n> `cache_tree_update()` to return a negative value.\n\nYeah, you're right.  Based on the conversation with Junio, I think I\ncan modify the write_index_as_tree() function slightly to do what we\nneed, then call it instead of write_tree_from_memory().  Since\nwrite_index_as_tree() doesn't try to output any error messages but\njust returns various error codes, I can then have the caller handle\nthe output.  So I'll do that instead of this patch and the previous\none.\n"},{"id":"379332","messageId":"nycvar.QRO.7.76.6.1907261323000.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"CABPp-BHahUojy1+cBrOTCvg-w3n-xJp3yE3Br6v8jMwnMzQwCQ@mail.gmail.com","subject":"Re: [PATCH 03/19] Ensure index matches head before invoking merge machinery, round N","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-26T11:23:33Z","receivedAt":"2019-07-26T11:23:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> On Thu, Jul 25, 2019 at 12:41 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Thu, 25 Jul 2019, Elijah Newren wrote:\n> >\n> > > diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\n> > > index 5b910e351e..a4bfd8fc51 100644\n> > > --- a/builtin/merge-recursive.c\n> > > +++ b/builtin/merge-recursive.c\n> > > @@ -1,3 +1,4 @@\n> > > +#include \"cache.h\"\n> > >  #include \"builtin.h\"\n> > >  #include \"commit.h\"\n> > >  #include \"tag.h\"\n> > > @@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n> > >       if (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n> > >               die(_(\"not handling anything other than two heads merge.\"));\n> > >\n> > > +     if (repo_read_index_unmerged(the_repository))\n> > > +             die_resolve_conflict(\"merge\");\n> >\n> > For a moment I was unsure whether `_unmerged()` is the right thing to do\n> > here, as it specifically allows to read the index even when there are\n> > conflict stages. But I guess it does not matter too much here. I\n> > probably would have opted for `repo_read_index()` instead, though.\n>\n> The names repo_read_index() and repo_read_index_unmerged() actually\n> seem slightly misleading to me; they seem to do the opposite of what\n> you'd think they do.\n>\n> repo_read_index() reads in an index and allows unmerged entries and\n> returns istate->cache_nr.\n>\n> repo_read_index_unmerged() calls repo_read_index(), then checks to see\n> if any of the entries are unmerged and returns whether or not any\n> unmerged entries were found.\n>\n> So, the way to disallow conflict stages isn't to use\n> repo_read_index(), but to use repo_read_index_unmerged(), as I did.\n> Counter-intuitive, I know.\n\nWhoa. No wonder I get it wrong all the time.\n\nThanks for educating me,\nDscho\n"},{"id":"379333","messageId":"nycvar.QRO.7.76.6.1907261325120.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"CABPp-BGpCEZ-40_SsT_vU+hCP=MiKzhky6yBr6G=UMnwLNR2Ow@mail.gmail.com","subject":"Re: [PATCH 15/19] merge-recursive: split internal fields into a separate struct","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-26T11:25:38Z","receivedAt":"2019-07-26T11:25:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 25 Jul 2019, Elijah Newren wrote:\n\n> On Thu, Jul 25, 2019 at 1:12 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > Hi Elijah,\n> >\n> > On Thu, 25 Jul 2019, Elijah Newren wrote:\n> >\n> > > merge_options has several internal fields that should not be set or read\n> > > by external callers.  This just complicates the API.  Move them into an\n> > > opaque merge_options_internal struct that is defined only in\n> > > merge-recursive.c and keep these out of merge-recursive.h.\n> >\n> > This method requires an extra `malloc()`/`free()` pair, leaving room for\n> > leaks.\n> >\n> > I'd rather keep the definition of that struct in `merge-recursive.h`,\n> > and make the `priv` field a full struct instead of a pointer. That would\n> > save on that extra allocation.\n>\n> Yes, it has one extra malloc()/free() pair for the entire merge operation.  But:\n>   * That's a drop in the bucket compared to the allocations already\n> performed during a merge\n>   * The allocation and free happen in merge_start() and\n> merge_finalize().  Not much to keep track of, so not much room for\n> leaks.\n>   * I want what's currently in merge-recursive.h to be as simple as\n> possible; I implemented your suggestion first, but it doesn't simplify\n> as much as possible.\n>\n> But, more importantly:\n>   * I want to write an alternative merge strategy providing drop-in\n> replacement functions for merge_trees(), merge_recursive(), and\n> merge_recursive_generic(). Defining merge_options_internal inside\n> merge-recursive.h would mean that I have to have _exactly_ the same\n> internal options in my implementation of merge-ort.c.  That doesn't\n> make sense.\n\nFair enough.\n\nI'm curious: what merge strategy are you planning on implementing?\n\nCiao,\nDscho\n"},{"id":"379340","messageId":"nycvar.QRO.7.76.6.1907261606570.21907@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"xmqq7e85pw3y.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 00/19] Cleanup merge API","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-26T14:07:47Z","receivedAt":"2019-07-26T14:07:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 25 Jul 2019, Junio C Hamano wrote:\n\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > On Thu, Jul 25, 2019 at 11:12 AM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> >> > Stuff I'd most welcome review on:\n> >> >   * Is cache-tree.c the right place for write_tree_from_memory()?\n> >> >     [see patch 7]  Should there be docs on how it differs from\n> >> >     write_index_as_tree(), already found in cache-tree?  What does\n> >> >     the latter even do?\n> >>\n> >> write_index_as_tree() is supposed to write the contents of an index\n> >> state as a tree object, and return the object ID for the resulting\n> >> tree.  It is the primary interface designed to be used by\n> >> write-tree.\n> >\n> > Other than the last sentence, that also sounds like the description of\n> > write_index_as_tree() -- at least as best I understood it.\n>\n> Yes, I didn't even know merge-recursive had its own variant.  I\n> suspect that back when \"merge-recursive in C\" was being developed,\n> it first used \"git write-tree\" via run_command(), and then just\n> copied and pasted what was done in the write-tree implementation\n> without bothering to refactor it into write_index_as_tree() and its\n> own bits about the unmerged index.\n\nFWIW that matches my understanding (i.e. one of those authors' of\n\"merge-recursive in C\").\n\nThanks,\nDscho\n"},{"id":"379353","messageId":"CABPp-BHUf0eR2Dj77+qNa0rS2qAHxQL2MpRgg26Zjrfheim6Gg@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1907252055500.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/19] Cleanup merge API","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:15:09Z","receivedAt":"2019-07-26T15:15:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Thu, Jul 25, 2019 at 12:15 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 25 Jul 2019, Elijah Newren wrote:\n>\n> >   * All current callers (3 of them?) of merge_recursive() always pass\n> >     it a specially created reversed-list for the merge_bases.  Some\n> >     history spelunking provides no details on any of these about why;\n> >     it appears that the 2nd and 3rd callers reversed the list because\n> >     the first did, and I'm guessing the first did in an attempt to\n> >     exactly match the git-merge-recursive.py scripts' behavior.\n>\n> That is part of the reason.\n>\n> After I worked on converting that Python script with Alex Riesen, I\n> tested it on all merge commits I could find at that point in linux.git,\n> to compare the outcomes between scripted and non-scripted versions. IIRC\n> I even reported those findings back to the mailing list.\n>\n> *clickety-click*\n>\n> Ah, I did the extensive test on git.git first [*1*]:\n> https://public-inbox.org/git/Pine.LNX.4.63.0608092232000.13885@wbgn013.biozentrum.uni-wuerzburg.de/\n>\n> As you can see, it seemed that the reverse order of merge bases (trying\n> to merge the oldest two merge bases first, then merging with the\n> 3rd-oldest, etc) avoided more merge conflicts than the original order.\n>\n> >     But if the API needs them in a reverse order from what people\n> >     would normally expect to pass them in, shouldn't it reverse them\n> >     itself instead of making all callers do it?\n>\n> I would not, as the order the merge bases are passed in defines the\n> order in which they are handled.\n>\n> >     Also, the order shouldn't matter when there are no conflicts, and\n> >     when there are conflicts it'd only change which side of the\n> >     conflict markers the made-up virtual merge base would list things\n> >     in.\n>\n> That's what I thought, right up until I re-created those merge commits.\n>\n> I really forgot most of the details, but I seem to remember that there\n> was a puzzling one where the reverse order caused no merge conflicts,\n> and the original order caused a double merge conflict.\n\nThanks for providing this history; very helpful.  In v2 (which I will\nbe posting soon), I included a comment in merge-recursive.h just above\nthe merge_recursive() function with a summary of these details:\n\nNOTE: empirically, about a decade ago it was determined that with more\n      than two merge bases, optimal behavior was found when the\n      merge_bases were passed in the order of oldest commit to newest\n      commit.  Also, merge_bases will be consumed (emptied) so make a\n      copy if you need it.\n"},{"id":"379355","messageId":"CABPp-BFRftNf8hoj8nTDiw+akci=w2qVAgamVDyRw1NUk-QCNA@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1907261325120.21907@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 15/19] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:30:46Z","receivedAt":"2019-07-26T15:36:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Fri, Jul 26, 2019 at 4:25 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> On Thu, 25 Jul 2019, Elijah Newren wrote:\n>\n> > On Thu, Jul 25, 2019 at 1:12 PM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n\n> > But, more importantly:\n> >   * I want to write an alternative merge strategy providing drop-in\n> > replacement functions for merge_trees(), merge_recursive(), and\n> > merge_recursive_generic(). Defining merge_options_internal inside\n> > merge-recursive.h would mean that I have to have _exactly_ the same\n> > internal options in my implementation of merge-ort.c.  That doesn't\n> > make sense.\n>\n> Fair enough.\n>\n> I'm curious: what merge strategy are you planning on implementing?\n\nrecursive, done right[1].  For now, I'm calling it \"Ostensibly\nRecursive's Twin\", or \"ort\" for short.  At first, people shouldn't be\nable to notice any difference between it and the current recursive\nstrategy, other than the fact that I think I can make it a bit faster\n(especially for big repos).  But it should allow me to fix some\n(admittedly corner case) bugs that are harder to handle in the current\ndesign, and I think that a merge that doesn't touch $GIT_WORK_TREE or\n$GIT_INDEX_FILE will allow for some fun new features.  That's the hope\nanyway.\n\n[1] https://public-inbox.org/git/xmqqd147kpdm.fsf@gitster.mtv.corp.google.com/\n"},{"id":"379356","messageId":"20190726155258.28561-2-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 01/20] merge-recursive: fix minor memory leak in error condition","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:39Z","receivedAt":"2019-07-26T15:53:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Returning before freeing the allocated buffer is suboptimal; as with\nelsewhere in the same function, make sure buf gets free'd.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 12300131fc..1163508811 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -934,9 +934,11 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \n \t\tbuf = read_object_file(&contents->oid, &type, &size);\n-\t\tif (!buf)\n-\t\t\treturn err(opt, _(\"cannot read object %s '%s'\"),\n-\t\t\t\t   oid_to_hex(&contents->oid), path);\n+\t\tif (!buf) {\n+\t\t\tret = err(opt, _(\"cannot read object %s '%s'\"),\n+\t\t\t\t  oid_to_hex(&contents->oid), path);\n+\t\t\tgoto free_buf;\n+\t\t}\n \t\tif (type != OBJ_BLOB) {\n \t\t\tret = err(opt, _(\"blob expected for %s '%s'\"),\n \t\t\t\t  oid_to_hex(&contents->oid), path);\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379357","messageId":"20190726155258.28561-1-newren@gmail.com","threadId":"51526","inReplyTo":"20190725174611.14802-1-newren@gmail.com","subject":"[PATCH v2 00/20] Cleanup merge API","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:38Z","receivedAt":"2019-07-26T15:53:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Before writing a replacement merge strategy for recursive, I decided\nto first cleanup the merge API -- streamlining merge-recursive.h and\nmaking it more readable.  It includes some fixes I noticed along the\nway, and two forgotten patches of mine from months ago that I rebased\nand included at the end.  As with v1:\n\n    While there are minor textual and semantic dependencies between\n    these patches (preventing me from splitting up this series), they\n    are logically separate and can be reviewed independently.\n\nNotable highlights (full range-diff below):\n  * Patches 8, 11, and 16 show up in the range-diff but have no\n    substantive changes (these diffs merely represents the minimal\n    changes needed to adjust due to the patches before them in the\n    series).  A few other patches (e.g. 9, 12) had some real changes\n    but most of the range-diff for those was similar noise.\n  * [Patch 4] Fixed a (pre-existing) memory leak pointed out by Dscho\n    and renamed a couple new variables to have a clearer meaning.\n  * [Patch 5, new] Added a new commit that simplified and clarified the\n    merge_trees() API by removing a parameter that would never be\n    written anyway.  I also added a comment to the header summarizing\n    some of Dscho's remarks about how best to pass the merge_bases to\n    the merge_recursive() function.\n  * [Patch 7] Made a small extension to write_index_as_tree(), deleted\n    write_tree_from_memory(), and converted callers of the latter into\n    callers of the former.  This patch replaces patches 6 & 7 from v1.\n  * [Patch 9] The only substantive change was to update the new comment\n    from patch 5 to also be affected by the parameter renaming that this\n    patch was for.\n  * [Patch 12] Added documentation carefully stating the expected outputs\n    of merge_trees(), merge_recursive(), and merge_recursive_generic()\n    and how they differ.\n  * [Patch 15, new] I noticed a case where we could accidentally lose\n    output and the memory holding it for future callers of merge_trees()\n    and fixed it.\n  * [Patch 20 (used to be 19)] Renamed a var Dscho pointed out as needing\n    a better name, added some asserts around how merge_trees() and\n    merge_recursive() have opposite expectations to avoid future callers\n    messing it up, and simplified the new t6047 testcase.\n\nStuff I'd most welcome review on:\n  * Patch 7 again -- do my changes to write_index_as_tree() look sane?\n  * Patches 5 & 15, since they are new and not reviewed last time.\n\nSome notes (same as last time, but still true):\n  * Applies on master, merges cleanly to next & pu\n  * Only patches 3, 5-7 touch anything outside of merge-recursive\n  * I'm going to be out next week (July 29-Aug 3), so I can only\n    respond to feedback today and tomorrow or it'll have to wait until\n    the 5th.\n\nElijah Newren (20):\n  merge-recursive: fix minor memory leak in error condition\n  merge-recursive: remove another implicit dependency on the_repository\n  Ensure index matches head before invoking merge machinery, round N\n  merge-recursive: exit early if index != head\n  merge-recursive: remove useless parameter in merge_trees()\n  merge-recursive: don't force external callers to do our logging\n  Use write_index_as_tree() in lieu of write_tree_from_memory()\n  merge-recursive: fix some overly long lines\n  merge-recursive: use common name for ancestors/common/base_list\n  merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n  merge-recursive: rename merge_options argument to opt in header\n  merge-recursive: move some definitions around to clean up the header\n  merge-recursive: consolidate unnecessary fields in merge_options\n  merge-recursive: comment and reorder the merge_options fields\n  merge-recursive: avoid losing output and leaking memory holding that\n    output\n  merge-recursive: split internal fields into a separate struct\n  merge-recursive: alphabetize include list\n  merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n  merge-recursive: be consistent with assert\n  merge-recursive: provide a better label for diff3 common ancestor\n\n builtin/checkout.c                |  10 +-\n builtin/merge-recursive.c         |   4 +\n builtin/stash.c                   |   2 +\n cache-tree.c                      |  36 +-\n cache-tree.h                      |   3 +-\n merge-recursive.c                 | 536 ++++++++++++++++++------------\n merge-recursive.h                 | 160 +++++----\n sequencer.c                       |   5 +-\n t/t3030-merge-recursive.sh        |   9 +-\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 189 +++++++++++\n 11 files changed, 646 insertions(+), 316 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\nRange-diff:\n 1:  28d4fb4710 =  1:  a640f0f2d0 merge-recursive: fix minor memory leak in error condition\n 2:  5aa56bacce =  2:  34f0891d96 merge-recursive: remove another implicit dependency on the_repository\n 3:  f38e2c4dcc =  3:  26739a7ed0 Ensure index matches head before invoking merge machinery, round N\n 4:  858ec5c6e7 !  4:  76cb459b99 merge-recursive: exit early if index != head\n    @@ -19,7 +19,8 @@\n         other callers (which were fixed in the commit prior to this one).\n     \n         Make sure we do the index == head check at the beginning of the merge,\n    -    and error out immediately if it fails.\n    +    and error out immediately if it fails.  While we're at it, fix a small\n    +    leak in the show-the-error codepath.\n     \n         Signed-off-by: Elijah Newren <newren@gmail.com>\n     \n    @@ -111,6 +112,7 @@\n     +\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n     +\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n     +\t\t    sb.buf);\n    ++\t\tstrbuf_release(&sb);\n     +\t\treturn -1;\n     +\t}\n     +\n    @@ -128,14 +130,14 @@\n     +\t\tstruct tree *common,\n     +\t\tstruct tree **result)\n     +{\n    -+\tint ret;\n    ++\tint clean;\n     +\n     +\tif (merge_start(opt, head))\n     +\t\treturn -1;\n    -+\tret = merge_trees_internal(opt, head, merge, common, result);\n    ++\tclean = merge_trees_internal(opt, head, merge, common, result);\n     +\tmerge_finalize(opt);\n     +\n    -+\treturn ret;\n    ++\treturn clean;\n     +}\n     +\n     +int merge_recursive(struct merge_options *opt,\n    @@ -144,14 +146,14 @@\n     +\t\t    struct commit_list *ca,\n     +\t\t    struct commit **result)\n     +{\n    -+\tint ret;\n    ++\tint clean;\n     +\n     +\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n     +\t\treturn -1;\n    -+\tret = merge_recursive_internal(opt, h1, h2, ca, result);\n    ++\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n     +\tmerge_finalize(opt);\n     +\n    -+\treturn ret;\n    ++\treturn clean;\n     +}\n     +\n      static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n -:  ---------- >  5:  2560458522 merge-recursive: remove useless parameter in merge_trees()\n 5:  71c37a0928 =  6:  fb340fbe56 merge-recursive: don't force external callers to do our logging\n 6:  286221dbf1 <  -:  ---------- Change call signature of write_tree_from_memory()\n 7:  725bda6b1d <  -:  ---------- Move write_tree_from_memory() from merge-recursive to cache-tree\n -:  ---------- >  7:  884305a3a6 Use write_index_as_tree() in lieu of write_tree_from_memory()\n 8:  d7fca78573 !  8:  870937b31f merge-recursive: fix some overly long lines\n    @@ -54,7 +54,7 @@\n      \n      \tfor (iter = ca; iter; iter = iter->next) {\n     @@\n    - \treturn ret;\n    + \treturn clean;\n      }\n      \n     -static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n 9:  6090301564 !  9:  5127b75ac4 merge-recursive: use common name for ancestors/common/base_list\n    @@ -146,19 +146,19 @@\n      int merge_trees(struct merge_options *opt,\n      \t\tstruct tree *head,\n      \t\tstruct tree *merge,\n    --\t\tstruct tree *common,\n    -+\t\tstruct tree *merge_base,\n    - \t\tstruct tree **result)\n    +-\t\tstruct tree *common)\n    ++\t\tstruct tree *merge_base)\n      {\n    - \tint ret;\n    + \tint clean;\n    + \tstruct tree *ignored;\n      \n      \tif (merge_start(opt, head))\n      \t\treturn -1;\n    --\tret = merge_trees_internal(opt, head, merge, common, result);\n    -+\tret = merge_trees_internal(opt, head, merge, merge_base, result);\n    +-\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n    ++\tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n      \tmerge_finalize(opt);\n      \n    - \treturn ret;\n    + \treturn clean;\n     @@\n      int merge_recursive(struct merge_options *opt,\n      \t\t    struct commit *h1,\n    @@ -167,15 +167,15 @@\n     +\t\t    struct commit_list *merge_bases,\n      \t\t    struct commit **result)\n      {\n    - \tint ret;\n    + \tint clean;\n      \n      \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n      \t\treturn -1;\n    --\tret = merge_recursive_internal(opt, h1, h2, ca, result);\n    -+\tret = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n    +-\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n    ++\tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n      \tmerge_finalize(opt);\n      \n    - \treturn ret;\n    + \treturn clean;\n     @@\n      int merge_recursive_generic(struct merge_options *opt,\n      \t\t\t    const struct object_id *head,\n    @@ -212,6 +212,16 @@\n      --- a/merge-recursive.h\n      +++ b/merge-recursive.h\n     @@\n    +  *\n    +  * NOTE: empirically, about a decade ago it was determined that with more\n    +  *       than two merge bases, optimal behavior was found when the\n    +- *       ancestors were passed in the order of oldest merge base to newest\n    +- *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n    +- *       you need it.\n    ++ *       merge_bases were passed in the order of oldest commit to newest\n    ++ *       commit.  Also, merge_bases will be consumed (emptied) so make a\n    ++ *       copy if you need it.\n    +  */\n      int merge_recursive(struct merge_options *o,\n      \t\t    struct commit *h1,\n      \t\t    struct commit *h2,\n    @@ -219,15 +229,16 @@\n     +\t\t    struct commit_list *merge_bases,\n      \t\t    struct commit **result);\n      \n    - /* rename-detecting three-way merge, no recursion */\n    + /*\n    +@@\n      int merge_trees(struct merge_options *o,\n      \t\tstruct tree *head,\n      \t\tstruct tree *merge,\n    --\t\tstruct tree *common,\n    -+\t\tstruct tree *merge_base,\n    - \t\tstruct tree **result);\n    +-\t\tstruct tree *common);\n    ++\t\tstruct tree *merge_base);\n      \n      /*\n    +  * \"git-merge-recursive\" can be fed trees; wrap them into\n     @@\n      int merge_recursive_generic(struct merge_options *o,\n      \t\t\t    const struct object_id *head,\n10:  33228a4b3d = 10:  daee364ce1 merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n11:  3be41685ad ! 11:  50a7a6f671 merge-recursive: rename merge_options argument to opt in header\n    @@ -25,20 +25,25 @@\n     +\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n      }\n      \n    - /* merge_trees() but with recursive ancestor consolidation */\n    + /*\n    +@@\n    +  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n    +  *       copy if you need it.\n    +  */\n     -int merge_recursive(struct merge_options *o,\n     +int merge_recursive(struct merge_options *opt,\n      \t\t    struct commit *h1,\n      \t\t    struct commit *h2,\n      \t\t    struct commit_list *merge_bases,\n    - \t\t    struct commit **result);\n    - \n    - /* rename-detecting three-way merge, no recursion */\n    +@@\n    +  * rename-detecting three-way merge, no recursion; result of merge is written\n    +  * to opt->repo->index.\n    +  */\n     -int merge_trees(struct merge_options *o,\n     +int merge_trees(struct merge_options *opt,\n      \t\tstruct tree *head,\n      \t\tstruct tree *merge,\n    - \t\tstruct tree *merge_base,\n    + \t\tstruct tree *merge_base);\n     @@\n       * \"git-merge-recursive\" can be fed trees; wrap them into\n       * virtual commits and call merge_recursive() proper.\n12:  b1c396e505 ! 12:  4e9e774dc5 merge-recursive: move some definitions around to clean up the header\n    @@ -74,7 +74,12 @@\n      \tstruct repository *repo;\n      };\n      \n    --/*\n    ++void init_merge_options(struct merge_options *opt, struct repository *repo);\n    ++\n    ++/* parse the option in s and update the relevant field of opt */\n    ++int parse_merge_opt(struct merge_options *opt, const char *s);\n    ++\n    + /*\n     - * For dir_rename_entry, directory names are stored as a full path from the\n     - * toplevel of the repository and do not include a trailing '/'.  Also:\n     - *\n    @@ -83,7 +88,12 @@\n     - *   new_dir:            final name of directory being renamed\n     - *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n     - *                       in get_directory_renames() for details\n    -- */\n    ++ * RETURN VALUES: All the merge_* functions below return a value as follows:\n    ++ *   > 0     Merge was clean\n    ++ *   = 0     Merge had conflicts\n    ++ *   < 0     Merge hit an unexpected and unrecoverable problem (e.g. disk\n    ++ *             full) and aborted merge part-way through.\n    +  */\n     -struct dir_rename_entry {\n     -\tstruct hashmap_entry ent; /* must be the first member! */\n     -\tchar *dir;\n    @@ -91,47 +101,80 @@\n     -\tstruct strbuf new_dir;\n     -\tstruct string_list possible_new_dirs;\n     -};\n    -+void init_merge_options(struct merge_options *opt, struct repository *repo);\n    - \n    +-\n     -struct collision_entry {\n     -\tstruct hashmap_entry ent; /* must be the first member! */\n     -\tchar *target_file;\n     -\tstruct string_list source_files;\n     -\tunsigned reported_already:1;\n     -};\n    -+/* parse the option in s and update the relevant field of opt */\n    -+int parse_merge_opt(struct merge_options *opt, const char *s);\n      \n     -static inline int merge_detect_rename(struct merge_options *opt)\n     -{\n     -\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n     -\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n     -}\n    -+/* rename-detecting three-way merge, no recursion */\n    ++/*\n    ++ * rename-detecting three-way merge, no recursion.\n    ++ *\n    ++ * Outputs:\n    ++ *   - See RETURN VALUES above\n    ++ *   - No commit is created\n    ++ *   - opt->repo->index has the new index\n    ++ *   - $GIT_INDEX_FILE is not updated\n    ++ *   - The working tree is updated with results of the merge\n    ++ */\n     +int merge_trees(struct merge_options *opt,\n     +\t\tstruct tree *head,\n     +\t\tstruct tree *merge,\n    -+\t\tstruct tree *merge_base,\n    -+\t\tstruct tree **result);\n    ++\t\tstruct tree *merge_base);\n      \n    - /* merge_trees() but with recursive ancestor consolidation */\n    + /*\n    +  * merge_recursive is like merge_trees() but with recursive ancestor\n    +- * consolidation, and when successful, it creates an actual commit\n    +- * and writes its address to *result.\n    ++ * consolidation and, if the commit is clean, creation of a commit.\n    +  *\n    +  * NOTE: empirically, about a decade ago it was determined that with more\n    +  *       than two merge bases, optimal behavior was found when the\n    +  *       merge_bases were passed in the order of oldest commit to newest\n    +  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n    +  *       copy if you need it.\n    ++ *\n    ++ * Outputs:\n    ++ *   - See RETURN VALUES above\n    ++ *   - If merge is clean, a commit is created and its address written to *result\n    ++ *   - opt->repo->index has the new index\n    ++ *   - $GIT_INDEX_FILE is not updated\n    ++ *   - The working tree is updated with results of the merge\n    +  */\n      int merge_recursive(struct merge_options *opt,\n    + \t\t    struct commit *h1,\n     @@\n    - \t\t    struct commit_list *merge_bases,\n      \t\t    struct commit **result);\n      \n    --/* rename-detecting three-way merge, no recursion */\n    + /*\n    +- * rename-detecting three-way merge, no recursion; result of merge is written\n    +- * to opt->repo->index.\n    +- */\n     -int merge_trees(struct merge_options *opt,\n     -\t\tstruct tree *head,\n     -\t\tstruct tree *merge,\n    --\t\tstruct tree *merge_base,\n    --\t\tstruct tree **result);\n    +-\t\tstruct tree *merge_base);\n     -\n    - /*\n    +-/*\n     - * \"git-merge-recursive\" can be fed trees; wrap them into\n     - * virtual commits and call merge_recursive() proper.\n     + * merge_recursive_generic can operate on trees instead of commits, by\n     + * wrapping the trees into virtual commits, and calling merge_recursive().\n    ++ * It also writes out the in-memory index to disk if the merge is successful.\n    ++ *\n    ++ * Outputs:\n    ++ *   - See RETURN VALUES above\n    ++ *   - If merge is clean, a commit is created and its address written to *result\n    ++ *   - opt->repo->index has the new index\n    ++ *   - $GIT_INDEX_FILE is updated\n    ++ *   - The working tree is updated with results of the merge\n       */\n      int merge_recursive_generic(struct merge_options *opt,\n      \t\t\t    const struct object_id *head,\n13:  bc653085af = 13:  bf40502fd8 merge-recursive: consolidate unnecessary fields in merge_options\n14:  28a8880890 = 14:  2c39a4be36 merge-recursive: comment and reorder the merge_options fields\n -:  ---------- > 15:  c1c71816eb merge-recursive: avoid losing output and leaking memory holding that output\n15:  8937e231d9 ! 16:  be47a6bfdf merge-recursive: split internal fields into a separate struct\n    @@ -525,12 +525,11 @@\n      \n      \tunpack_trees_finish(opt);\n      \n    --\tif (opt->call_depth && !(*result = write_tree_from_memory(opt->repo)))\n    -+\tif (opt->priv->call_depth &&\n    -+\t    !(*result = write_tree_from_memory(opt->repo)))\n    - \t\treturn -1;\n    - \n    - \treturn clean;\n    +-\tif (opt->call_depth) {\n    ++\tif (opt->priv->call_depth) {\n    + \t\tstruct object_id tree_id;\n    + \t\tif (write_index_as_tree(&tree_id, opt->repo->index, NULL,\n    + \t\t\t\t\tWRITE_TREE_FROM_MEMORY, NULL) ||\n     @@\n      \n      \tfor (iter = merge_bases; iter; iter = iter->next) {\n    @@ -566,14 +565,6 @@\n      \t\t*result = make_virtual_commit(opt->repo, result_tree,\n      \t\t\t\t\t      \"merged tree\");\n      \t\tcommit_list_insert(h1, &(*result)->parents);\n    - \t\tcommit_list_insert(h2, &(*result)->parents->next);\n    - \t}\n    - \tflush_output(opt);\n    --\tif (!opt->call_depth && opt->buffer_output < 2)\n    -+\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n    - \t\tstrbuf_release(&opt->obuf);\n    - \treturn clean;\n    - }\n     @@\n      \t\treturn -1;\n      \t}\n    @@ -583,8 +574,12 @@\n      \treturn 0;\n      }\n      \n    -@@\n    + static void merge_finalize(struct merge_options *opt)\n      {\n    + \tflush_output(opt);\n    +-\tif (!opt->call_depth && opt->buffer_output < 2)\n    ++\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n    + \t\tstrbuf_release(&opt->obuf);\n      \tif (show(opt, 2))\n      \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     -\t\t\t\t       opt->needed_rename_limit, 0);\n16:  0ba049d6a2 = 17:  f440ee1e64 merge-recursive: alphabetize include list\n17:  43eed3490b = 18:  40161dc352 merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n18:  3afc6d987a = 19:  d6158e555d merge-recursive: be consistent with assert\n19:  ac7176acaf ! 20:  814a65ecab merge-recursive: provide a better label for diff3 common ancestor\n    @@ -81,7 +81,7 @@\n      \tstruct tree *result_tree;\n      \tint clean;\n     +\tint num_merge_bases;\n    -+\tstruct strbuf commit_name = STRBUF_INIT;\n    ++\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n      \n      \tif (show(opt, 4)) {\n      \t\toutput(opt, 4, _(\"Merging:\"));\n    @@ -103,10 +103,10 @@\n     +\t\topt->ancestor = \"<empty tree>\";\n     +\t\tbreak;\n     +\tcase 1:\n    -+\t\tstrbuf_add_unique_abbrev(&commit_name,\n    ++\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n     +\t\t\t\t\t &merged_merge_bases->object.oid,\n     +\t\t\t\t\t DEFAULT_ABBREV);\n    -+\t\topt->ancestor = commit_name.buf;\n    ++\t\topt->ancestor = merge_base_abbrev.buf;\n     +\t\tbreak;\n     +\tdefault:\n     +\t\topt->ancestor = \"merged common ancestors\";\n    @@ -117,10 +117,28 @@\n      \t\t\t\t     repo_get_commit_tree(opt->repo,\n      \t\t\t\t\t\t\t  merged_merge_bases),\n      \t\t\t\t     &result_tree);\n    -+\tstrbuf_release(&commit_name);\n    ++\tstrbuf_release(&merge_base_abbrev);\n      \tif (clean < 0) {\n      \t\tflush_output(opt);\n      \t\treturn clean;\n    +@@\n    + \tint clean;\n    + \tstruct tree *ignored;\n    + \n    ++\tassert(opt->ancestor != NULL);\n    ++\n    + \tif (merge_start(opt, head))\n    + \t\treturn -1;\n    + \tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n    +@@\n    + {\n    + \tint clean;\n    + \n    ++\tassert(opt->ancestor == NULL);\n    ++\n    + \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n    + \t\treturn -1;\n    + \tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n     \n      diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\n      --- a/t/t6036-recursive-corner-cases.sh\n    @@ -196,20 +214,11 @@\n     +\t(\n     +\t\tcd no_merge_base &&\n     +\n    -+\t\tgit checkout --orphan L &&\n    -+\t\ttest_seq 1 9 >content &&\n    -+\t\techo \"A\" >>content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version L1 of content\" &&\n    ++\t\tgit checkout -b L &&\n    ++\t\ttest_commit A content A &&\n     +\n    -+\t\t# Create R\n     +\t\tgit checkout --orphan R &&\n    -+\t\ttest_seq 1 9 >content &&\n    -+\t\techo \"10\" >>content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version R1 of content\"\n    ++\t\ttest_commit B content B\n     +\t)\n     +'\n     +\n    @@ -241,30 +250,32 @@\n     +\t(\n     +\t\tcd unique_merge_base &&\n     +\n    -+\t\ttest_seq 1 9 >content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m initial &&\n    ++\t\ttest_commit base content \"1\n    ++2\n    ++3\n    ++4\n    ++5\n    ++\" &&\n     +\n     +\t\tgit branch L &&\n     +\t\tgit branch R &&\n     +\n    -+\t\t# Create L1\n     +\t\tgit checkout L &&\n    -+\t\ttest_seq 0 9 >content &&\n    -+\t\techo \"A\" >>content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version L1 of content\" &&\n    ++\t\ttest_commit L content \"1\n    ++2\n    ++3\n    ++4\n    ++5\n    ++7\" &&\n     +\n    -+\t\t# Create R1\n     +\t\tgit checkout R &&\n    -+\t\ttest_seq 0 9 >content &&\n    -+\t\techo \"ten\" >>content &&\n    -+\t\tgit add content &&\n    -+\t\tgit mv content renamed &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version R1 of content\"\n    ++\t\tgit rm content &&\n    ++\t\ttest_commit R renamed \"1\n    ++2\n    ++3\n    ++4\n    ++5\n    ++six\"\n     +\t)\n     +'\n     +\n    @@ -300,30 +311,32 @@\n     +\t(\n     +\t\tcd multiple_merge_bases &&\n     +\n    -+\t\t# Create some related files now\n    -+\t\ttest_seq 1 9 >content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m initial &&\n    ++\t\ttest_commit initial content \"1\n    ++2\n    ++3\n    ++4\n    ++5\" &&\n     +\n     +\t\tgit branch L &&\n     +\t\tgit branch R &&\n     +\n     +\t\t# Create L1\n     +\t\tgit checkout L &&\n    -+\t\ttest_seq 0 9 >content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version L1 of content\" &&\n    -+\t\tgit tag L1 &&\n    ++\t\ttest_commit L1 content \"0\n    ++1\n    ++2\n    ++3\n    ++4\n    ++5\" &&\n     +\n     +\t\t# Create R1\n     +\t\tgit checkout R &&\n    -+\t\ttest_seq 1 10 >content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"verson R1 of content\" &&\n    -+\t\tgit tag R1 &&\n    ++\t\ttest_commit R1 content \"1\n    ++2\n    ++3\n    ++4\n    ++5\n    ++6\" &&\n     +\n     +\t\t# Create L2\n     +\t\tgit checkout L &&\n    @@ -335,20 +348,23 @@\n     +\n     +\t\t# Create L3\n     +\t\tgit checkout L &&\n    -+\t\ttest_seq 0 9 >content &&\n    -+\t\techo \"A\" >>content &&\n    -+\t\tgit add content &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version L3 of content\" &&\n    ++\t\ttest_commit L3 content \"0\n    ++1\n    ++2\n    ++3\n    ++4\n    ++5\n    ++A\" &&\n     +\n     +\t\t# Create R3\n     +\t\tgit checkout R &&\n    -+\t\ttest_seq 0 9 >content &&\n    -+\t\techo \"ten\" >>content &&\n    -+\t\tgit add content &&\n    -+\t\tgit mv content renamed &&\n    -+\t\ttest_tick &&\n    -+\t\tgit commit -m \"version R3 of content\"\n    ++\t\tgit rm content &&\n    ++\t\ttest_commit R3 renamed \"0\n    ++2\n    ++3\n    ++4\n    ++5\n    ++six\"\n     +\t)\n     +'\n     +\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379359","messageId":"20190726155258.28561-4-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 03/20] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:41Z","receivedAt":"2019-07-26T15:53:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is the bug that just won't die; there always seems to be another\nform of it somewhere.  See the commit message of 55f39cf7551b (\"merge:\nfix misleading pre-merge check documentation\", 2018-06-30) for a more\ndetailed explanation), but in short:\n\n<quick summary>\n\nbuiltin/merge.c contains this important requirement for merge\nstrategies:\n\n    ...the index must be in sync with the head commit.  The strategies are\n    responsible to ensure this.\n\nThis condition is important to enforce because there are two likely\nfailure cases when the index isn't in sync with the head commit:\n\n  * we silently throw away changes the user had staged before the merge\n\n  * we accidentally (and silently) include changes in the merge that\n    were not part of either of the branches/trees being merged\n\nDiscarding users' work and mis-merging are both bad outcomes, especially\nwhen done silently, so naturally this rule was stated sternly -- but,\nunfortunately totally ignored in practice unless and until actual bugs\nwere found.  But, fear not: the bugs from this were fixed in commit\n  ee6566e8d70d (\"[PATCH] Rewrite read-tree\", 2005-09-05)\nthrough a rewrite of read-tree (again, commit 55f39cf7551b has a more\ndetailed explanation of how this affected merge).  And it was fixed\nagain in commit\n  160252f81626 (\"git-merge-ours: make sure our index matches HEAD\", 2005-11-03)\n...and it was fixed again in commit\n  3ec62ad9ffba (\"merge-octopus: abort if index does not match HEAD\", 2016-04-09)\n...and again in commit\n  65170c07d466 (\"merge-recursive: avoid incorporating uncommitted changes in a merge\", 2017-12-21)\n...and again in commit\n  eddd1a411d93 (\"merge-recursive: enforce rule that index matches head before merging\", 2018-06-30)\n\n...with multiple testcases added to the testsuite that could be\nenumerated in even more commits.\n\nThen, finally, in a patch in the same series as the last fix above, the\ndocumentation about this requirement was fixed in commit 55f39cf7551b\n(\"merge: fix misleading pre-merge check documentation\", 2018-06-30), and\nwe all lived happily ever after...\n\n</quick summary>\n\nUnfortunately, \"ever after\" apparently denotes a limited time and it\nexpired today.  The merge-recursive rule to enforce that index matches\nhead was at the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially HUGE amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nTrying to enforce that all of merge_trees(), merge_recursive(), and\nmerge_recursive_generic() checked the index == head condition earlier\nresulted in a bunch of broken tests.  It turns out that\nmerge_recursive() has code to drop and reload the cache while recursing\nto create intermediate virtual merge bases, but unfortunately that code\nruns even when no recursion is necessary.  This unconditional dropping\nand reloading of the cache masked a few bugs:\n\n  * builtin/merge-recursive.c: didn't even bother loading the index.\n\n  * builtin/stash.c: feels like a fake 'builtin' because it repeatedly\n    invokes git subprocesses all over the place, mixed with other\n    operations.  In particular, invoking \"git reset\" will reset the\n    index on disk, but the parent process that invoked it won't\n    automatically have its in-memory index updated.\n\n  * t3030-merge-recursive.h: this test has always been broken in that it\n    didn't make sure to make index match head before running.  But, it\n    didn't care about the index or even the merge result, just the\n    verbose output while running.  While commit eddd1a411d93\n    (\"merge-recursive: enforce rule that index matches head before\n    merging\", 2018-06-30) should have uncovered this broken test, it\n    used a test_must_fail wrapper around the merge-recursive call\n    because it was known that the merge resulted in a rename/rename\n    conflict.  Thus, that fix only made this test fail for a different\n    reason, and since the index == head check didn't happen until after\n    coming all the way back out of the recursion, the testcase had\n    enough information to pass the one check that it did perform.\n\nSo, load the index in builtin/merge-recursive.c, reload the in-memory\nindex in builtin/stash.c, and modify the t3030 testcase to correctly\nsetup the index and make sure that the test fails in the expected way\n(meaning it reports a rename/rename conflict).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-recursive.c  | 4 ++++\n builtin/stash.c            | 2 ++\n t/t3030-merge-recursive.sh | 9 ++++++++-\n 3 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex 5b910e351e..a4bfd8fc51 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -1,3 +1,4 @@\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n #include \"tag.h\"\n@@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n \t\tdie(_(\"not handling anything other than two heads merge.\"));\n \n+\tif (repo_read_index_unmerged(the_repository))\n+\t\tdie_resolve_conflict(\"merge\");\n+\n \to.branch1 = argv[++i];\n \to.branch2 = argv[++i];\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex b5a301f24d..4aa47785f9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \t\t\t\treturn error(_(\"could not save index tree\"));\n \n \t\t\treset_head();\n+\t\t\tdiscard_cache();\n+\t\t\tread_cache();\n \t\t}\n \t}\n \ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex ff641b348a..a37bcc58a0 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '\n test_expect_success 'merge-recursive remembers the names of all base trees' '\n \tgit reset --hard HEAD &&\n \n+\t# make the index match $c1 so that merge-recursive below does not\n+\t# fail early\n+\tgit diff --binary HEAD $c1 -- | git apply --cached &&\n+\n \t# more trees than static slots used by oid_to_hex()\n \tfor commit in $c0 $c2 $c4 $c5 $c6 $c7\n \tdo\n \t\tgit rev-parse \"$commit^{tree}\"\n \tdone >trees &&\n \n-\t# ignore the return code -- it only fails because the input is weird\n+\t# ignore the return code; it only fails because the input is weird...\n \ttest_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&\n \n+\t# ...but make sure it fails in the expected way\n+\ttest_i18ngrep CONFLICT.*rename/rename out &&\n+\n \t# merge-recursive prints in reverse order, but we do not care\n \tsort <trees >expect &&\n \tsed -n \"s/^virtual //p\" out | sort >actual &&\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379358","messageId":"20190726155258.28561-3-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 02/20] merge-recursive: remove another implicit dependency on the_repository","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:40Z","receivedAt":"2019-07-26T15:53:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Commit d7cf3a96e9a0 (\"merge-recursive.c: remove implicit dependency on\nthe_repository\", 2019-01-12) and follow-ups like commit 34e7771bc644\n(\"Use the right 'struct repository' instead of the_repository\",\n2019-06-27), removed most implicit uses of the_repository.  Convert\ncalls to get_commit_tree() to instead use repo_get_commit_tree() to get\nrid of another.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1163508811..37bb94fb4d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3571,8 +3571,11 @@ int merge_recursive(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = \"merged common ancestors\";\n-\tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n-\t\t\t    get_commit_tree(merged_common_ancestors),\n+\tclean = merge_trees(opt,\n+\t\t\t    repo_get_commit_tree(opt->repo, h1),\n+\t\t\t    repo_get_commit_tree(opt->repo, h2),\n+\t\t\t    repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t merged_common_ancestors),\n \t\t\t    &mrtree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379360","messageId":"20190726155258.28561-5-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 04/20] merge-recursive: exit early if index != head","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:42Z","receivedAt":"2019-07-26T15:53:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We had a rule to enforce that the index matches head, but it was found\nat the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially huge amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nFurther, not enforcing this requirement earlier allowed other bugs (such\nas an unintentional unconditional dropping and reloading of the index in\nmerge_recursive() even when no recursion was necessary), to mask bugs in\nother callers (which were fixed in the commit prior to this one).\n\nMake sure we do the index == head check at the beginning of the merge,\nand error out immediately if it fails.  While we're at it, fix a small\nleak in the show-the-error codepath.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 94 +++++++++++++++++++++++++++++++++++------------\n 1 file changed, 70 insertions(+), 24 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 37bb94fb4d..f5cf21e2e0 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3381,21 +3381,14 @@ static int process_entry(struct merge_options *opt,\n \treturn clean_merge;\n }\n \n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+static int merge_trees_internal(struct merge_options *opt,\n+\t\t\t\tstruct tree *head,\n+\t\t\t\tstruct tree *merge,\n+\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n-\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n-\t\t    sb.buf);\n-\t\treturn -1;\n-\t}\n \n \tif (opt->subtree_shift) {\n \t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n@@ -3499,11 +3492,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n  * Merge the commits h1 and h2, return the resulting virtual\n  * commit object and a flag indicating the cleanness of the merge.\n  */\n-int merge_recursive(struct merge_options *opt,\n-\t\t    struct commit *h1,\n-\t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n-\t\t    struct commit **result)\n+static int merge_recursive_internal(struct merge_options *opt,\n+\t\t\t\t    struct commit *h1,\n+\t\t\t\t    struct commit *h2,\n+\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_common_ancestors;\n@@ -3555,7 +3548,7 @@ int merge_recursive(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive(opt, merged_common_ancestors, iter->item,\n+\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n \t\t\t\t    NULL, &merged_common_ancestors) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n@@ -3571,12 +3564,12 @@ int merge_recursive(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = \"merged common ancestors\";\n-\tclean = merge_trees(opt,\n-\t\t\t    repo_get_commit_tree(opt->repo, h1),\n-\t\t\t    repo_get_commit_tree(opt->repo, h2),\n-\t\t\t    repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t merged_common_ancestors),\n-\t\t\t    &mrtree);\n+\tclean = merge_trees_internal(opt,\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n+\t\t\t\t     repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t     &mrtree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\n@@ -3596,6 +3589,59 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n+static int merge_start(struct merge_options *opt, struct tree *head)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tassert(opt->branch1 && opt->branch2);\n+\n+\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merge_finalize(struct merge_options *opt)\n+{\n+\t/* Common code for wrapping up merges will be added here later */\n+}\n+\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *common,\n+\t\tstruct tree **result)\n+{\n+\tint clean;\n+\n+\tif (merge_start(opt, head))\n+\t\treturn -1;\n+\tclean = merge_trees_internal(opt, head, merge, common, result);\n+\tmerge_finalize(opt);\n+\n+\treturn clean;\n+}\n+\n+int merge_recursive(struct merge_options *opt,\n+\t\t    struct commit *h1,\n+\t\t    struct commit *h2,\n+\t\t    struct commit_list *ca,\n+\t\t    struct commit **result)\n+{\n+\tint clean;\n+\n+\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n+\t\treturn -1;\n+\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tmerge_finalize(opt);\n+\n+\treturn clean;\n+}\n+\n static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n \t\t\t      const char *name)\n {\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379361","messageId":"20190726155258.28561-6-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 05/20] merge-recursive: remove useless parameter in merge_trees()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:43Z","receivedAt":"2019-07-26T15:53:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees() took a results parameter that would only be written when\nopt->call_depth was positive, which is never the case now that\nmerge_trees_internal() has been split from merge_trees().  Remove the\nmisleading and unused parameter from merge_trees().\n\nWhile at it, add some comments explaining how the output of\nmerge_trees() and merge_recursive() differ.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  4 +---\n merge-recursive.c  |  6 +++---\n merge-recursive.h  | 20 ++++++++++++++++----\n sequencer.c        |  4 ++--\n 4 files changed, 22 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 91f8509f85..20e38c5edc 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -708,7 +708,6 @@ static int merge_working_tree(const struct checkout_opts *opts,\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 *result;\n \t\t\tstruct tree *work;\n \t\t\tstruct tree *old_tree;\n \t\t\tstruct merge_options o;\n@@ -773,8 +772,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = merge_trees(&o,\n \t\t\t\t\t  new_tree,\n \t\t\t\t\t  work,\n-\t\t\t\t\t  old_tree,\n-\t\t\t\t\t  &result);\n+\t\t\t\t\t  old_tree);\n \t\t\tif (ret < 0)\n \t\t\t\texit(128);\n \t\t\tret = reset_tree(new_tree,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f5cf21e2e0..4a481c3929 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3613,14 +3613,14 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+\t\tstruct tree *common)\n {\n \tint clean;\n+\tstruct tree *ignored;\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tclean = merge_trees_internal(opt, head, merge, common, result);\n+\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n \tmerge_finalize(opt);\n \n \treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c2b7bb65c6..812c456f1b 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -70,19 +70,31 @@ static inline int merge_detect_rename(struct merge_options *o)\n \t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n }\n \n-/* merge_trees() but with recursive ancestor consolidation */\n+/*\n+ * merge_recursive is like merge_trees() but with recursive ancestor\n+ * consolidation, and when successful, it creates an actual commit\n+ * and writes its address to *result.\n+ *\n+ * NOTE: empirically, about a decade ago it was determined that with more\n+ *       than two merge bases, optimal behavior was found when the\n+ *       ancestors were passed in the order of oldest merge base to newest\n+ *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n+ *       you need it.\n+ */\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *ancestors,\n \t\t    struct commit **result);\n \n-/* rename-detecting three-way merge, no recursion */\n+/*\n+ * rename-detecting three-way merge, no recursion; result of merge is written\n+ * to opt->repo->index.\n+ */\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result);\n+\t\tstruct tree *common);\n \n /*\n  * \"git-merge-recursive\" can be fed trees; wrap them into\ndiff --git a/sequencer.c b/sequencer.c\nindex 34ebf8ed94..c4ed30f1b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -586,7 +586,7 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n-\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n \tint clean;\n \tchar **xopt;\n \tstruct lock_file index_lock = LOCK_INIT;\n@@ -613,7 +613,7 @@ static int do_recursive_merge(struct repository *r,\n \n \tclean = merge_trees(&o,\n \t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree, &result);\n+\t\t\t    next_tree, base_tree);\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379363","messageId":"20190726155258.28561-8-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 07/20] Use write_index_as_tree() in lieu of write_tree_from_memory()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:45Z","receivedAt":"2019-07-26T15:53:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"write_tree_from_memory() appeared to be a merge-recursive special that\nbasically duplicated write_index_as_tree().  The two had a slightly\ndifferent call structure but the big difference was just that\nwrite_index_as_tree() would always unconditionally read the index off of\ndisk instead of working on the current in-memory index.  Add a flag to\nallow using the in-memory index, and then switch over to\nwrite_index_as_tree().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  6 +++++-\n cache-tree.c       | 36 +++++++++++++++++++++++-------------\n cache-tree.h       |  3 ++-\n merge-recursive.c  | 42 +++++++++---------------------------------\n merge-recursive.h  |  1 -\n 5 files changed, 39 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 20e38c5edc..a7b6454061 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -709,6 +709,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t * whether the merge flag was used.\n \t\t\t */\n \t\t\tstruct tree *work;\n+\t\t\tstruct object_id work_oid;\n \t\t\tstruct tree *old_tree;\n \t\t\tstruct merge_options o;\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n@@ -759,7 +760,10 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t */\n \t\t\tinit_merge_options(&o, the_repository);\n \t\t\to.verbosity = 0;\n-\t\t\twork = write_tree_from_memory(&o);\n+\t\t\tif (write_index_as_tree(&work_oid, &the_index, NULL,\n+\t\t\t\t\tWRITE_TREE_FROM_MEMORY, NULL) ||\n+\t\t\t    !(work = lookup_tree(the_repository, &work_oid)))\n+\t\t\t\tdie(_(\"error building trees\"));\n \n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 706ffcf188..99144b1704 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -613,14 +613,19 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \tint entries, was_valid;\n \tstruct lock_file lock_file = LOCK_INIT;\n \tint ret = 0;\n+\tint access_disk = !(flags & WRITE_TREE_FROM_MEMORY);\n \n-\thold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);\n+\tif (access_disk) {\n+\t\thold_lock_file_for_update(&lock_file, index_path,\n+\t\t\t\t\t  LOCK_DIE_ON_ERROR);\n \n-\tentries = read_index_from(index_state, index_path, get_git_dir());\n-\tif (entries < 0) {\n-\t\tret = WRITE_TREE_UNREADABLE_INDEX;\n-\t\tgoto out;\n+\t\tentries = read_index_from(index_state, index_path, get_git_dir());\n+\t\tif (entries < 0) {\n+\t\t\tret = WRITE_TREE_UNREADABLE_INDEX;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n+\n \tif (flags & WRITE_TREE_IGNORE_CACHE_TREE)\n \t\tcache_tree_free(&index_state->cache_tree);\n \n@@ -633,13 +638,16 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \t\t\tret = WRITE_TREE_UNMERGED_INDEX;\n \t\t\tgoto out;\n \t\t}\n-\t\twrite_locked_index(index_state, &lock_file, COMMIT_LOCK);\n-\t\t/* Not being able to write is fine -- we are only interested\n-\t\t * in updating the cache-tree part, and if the next caller\n-\t\t * ends up using the old index with unupdated cache-tree part\n-\t\t * it misses the work we did here, but that is just a\n-\t\t * performance penalty and not a big deal.\n-\t\t */\n+\t\tif (access_disk) {\n+\t\t\twrite_locked_index(index_state, &lock_file, COMMIT_LOCK);\n+\t\t\t/* Not being able to write is fine -- we are only\n+\t\t\t * interested in updating the cache-tree part, and if\n+\t\t\t * the next caller ends up using the old index with\n+\t\t\t * unupdated cache-tree part it misses the work we\n+\t\t\t * did here, but that is just a performance penalty\n+\t\t\t * and not a big deal.\n+\t\t\t */\n+\t\t}\n \t}\n \n \tif (prefix) {\n@@ -655,7 +663,9 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \t\toidcpy(oid, &index_state->cache_tree->oid);\n \n out:\n-\trollback_lock_file(&lock_file);\n+\tif (access_disk)\n+\t\trollback_lock_file(&lock_file);\n+\n \treturn ret;\n }\n \ndiff --git a/cache-tree.h b/cache-tree.h\nindex 757bbc48bc..481ec45dfa 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -34,12 +34,13 @@ int cache_tree_fully_valid(struct cache_tree *);\n int cache_tree_update(struct index_state *, int);\n void cache_tree_verify(struct repository *, struct index_state *);\n \n-/* bitmasks to write_cache_as_tree flags */\n+/* bitmasks to write_index_as_tree flags */\n #define WRITE_TREE_MISSING_OK 1\n #define WRITE_TREE_IGNORE_CACHE_TREE 2\n #define WRITE_TREE_DRY_RUN 4\n #define WRITE_TREE_SILENT 8\n #define WRITE_TREE_REPAIR 16\n+#define WRITE_TREE_FROM_MEMORY 32\n \n /* error return codes */\n #define WRITE_TREE_UNREADABLE_INDEX (-1)\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 308e350ff1..6c38c02e3f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -412,37 +412,6 @@ static void unpack_trees_finish(struct merge_options *opt)\n \tclear_unpack_trees_porcelain(&opt->unpack_opts);\n }\n \n-struct tree *write_tree_from_memory(struct merge_options *opt)\n-{\n-\tstruct tree *result = NULL;\n-\tstruct index_state *istate = opt->repo->index;\n-\n-\tif (unmerged_index(istate)) {\n-\t\tint i;\n-\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n-\t\tfor (i = 0; i < istate->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = istate->cache[i];\n-\t\t\tif (ce_stage(ce))\n-\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n-\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n-\t\t}\n-\t\tBUG(\"unmerged index entries in merge-recursive.c\");\n-\t}\n-\n-\tif (!istate->cache_tree)\n-\t\tistate->cache_tree = cache_tree();\n-\n-\tif (!cache_tree_fully_valid(istate->cache_tree) &&\n-\t    cache_tree_update(istate, 0) < 0) {\n-\t\terr(opt, _(\"error building trees\"));\n-\t\treturn NULL;\n-\t}\n-\n-\tresult = lookup_tree(opt->repo, &istate->cache_tree->oid);\n-\n-\treturn result;\n-}\n-\n static int save_files_dirs(const struct object_id *oid,\n \t\t\t   struct strbuf *base, const char *path,\n \t\t\t   unsigned int mode, int stage, void *context)\n@@ -3471,8 +3440,15 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth && !(*result = write_tree_from_memory(opt)))\n-\t\treturn -1;\n+\tif (opt->call_depth) {\n+\t\tstruct object_id tree_id;\n+\t\tif (write_index_as_tree(&tree_id, opt->repo->index, NULL,\n+\t\t\t\t\tWRITE_TREE_FROM_MEMORY, NULL) ||\n+\t\t    !(*result = lookup_tree(opt->repo, &tree_id))) {\n+\t\t\terr(opt, _(\"error building trees\"));\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n \n \treturn clean;\n }\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 812c456f1b..b3394502c7 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -109,7 +109,6 @@ int merge_recursive_generic(struct merge_options *o,\n \n void init_merge_options(struct merge_options *o,\n \t\t\tstruct repository *repo);\n-struct tree *write_tree_from_memory(struct merge_options *o);\n \n int parse_merge_opt(struct merge_options *out, const char *s);\n \n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379364","messageId":"20190726155258.28561-9-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 08/20] merge-recursive: fix some overly long lines","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:46Z","receivedAt":"2019-07-26T15:53:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code change, just add some line breaks to fix lines that\nhave grown in length due to various refactorings.  Most remaining lines\nof excessive length in merge-recursive include error messages and it's\nnot clear that splitting those improves things.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 19 +++++++++++++------\n 1 file changed, 13 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 6c38c02e3f..e81dec8f1f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -681,7 +681,9 @@ static void add_flattened_path(struct strbuf *out, const char *s)\n \t\t\tout->buf[i] = '_';\n }\n \n-static char *unique_path(struct merge_options *opt, const char *path, const char *branch)\n+static char *unique_path(struct merge_options *opt,\n+\t\t\t const char *path,\n+\t\t\t const char *branch)\n {\n \tstruct path_hashmap_entry *entry;\n \tstruct strbuf newpath = STRBUF_INIT;\n@@ -915,7 +917,8 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \t\tif (S_ISREG(contents->mode)) {\n \t\t\tstruct strbuf strbuf = STRBUF_INIT;\n-\t\t\tif (convert_to_working_tree(opt->repo->index, path, buf, size, &strbuf)) {\n+\t\t\tif (convert_to_working_tree(opt->repo->index,\n+\t\t\t\t\t\t    path, buf, size, &strbuf)) {\n \t\t\t\tfree(buf);\n \t\t\t\tsize = strbuf.len;\n \t\t\t\tbuf = strbuf_detach(&strbuf, NULL);\n@@ -3392,7 +3395,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp, NULL, 512);\n+\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n \n@@ -3505,7 +3509,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n+\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n+\t\t\t\t\t\t\t      tree, \"ancestor\");\n \t}\n \n \tfor (iter = ca; iter; iter = iter->next) {\n@@ -3617,7 +3622,8 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n-static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n+static struct commit *get_ref(struct repository *repo,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const char *name)\n {\n \tstruct object *object;\n@@ -3652,7 +3658,8 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\tint i;\n \t\tfor (i = 0; i < num_base_list; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i], oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n+\t\t\t\t\t     oid_to_hex(base_list[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n \t\t\t\t\t   oid_to_hex(base_list[i]));\n \t\t\tcommit_list_insert(base, &ca);\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379362","messageId":"20190726155258.28561-7-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 06/20] merge-recursive: don't force external callers to do our logging","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:44Z","receivedAt":"2019-07-26T15:53:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Alternatively, you can view this as \"make the merge functions behave\nmore similarly.\"  merge-recursive has three different entry points:\nmerge_trees(), merge_recursive(), and merge_recursive_generic().  Two of\nthese would call diff_warn_rename_limit(), but merge_trees() didn't.\nThis lead to callers of merge_trees() needing to manually call\ndiff_warn_rename_limit() themselves.  Move this to the new\nmerge_finalize() function to make sure that all three entry points run\nthis function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++----\n sequencer.c       | 1 -\n 2 files changed, 3 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 4a481c3929..308e350ff1 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3583,9 +3583,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tflush_output(opt);\n \tif (!opt->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n-\tif (show(opt, 2))\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n \treturn clean;\n }\n \n@@ -3607,7 +3604,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n-\t/* Common code for wrapping up merges will be added here later */\n+\tif (show(opt, 2))\n+\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t\t       opt->needed_rename_limit, 0);\n }\n \n int merge_trees(struct merge_options *opt,\ndiff --git a/sequencer.c b/sequencer.c\nindex c4ed30f1b4..094a4dd03d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -617,7 +617,6 @@ static int do_recursive_merge(struct repository *r,\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-\tdiff_warn_rename_limit(\"merge.renamelimit\", o.needed_rename_limit, 0);\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379365","messageId":"20190726155258.28561-11-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 10/20] merge-recursive: rename 'mrtree' to 'result_tree', for clarity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:48Z","receivedAt":"2019-07-26T15:53:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"It is not at all clear what 'mr' was supposed to stand for, at least not\nto me.  Pick a clearer name for this variable.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8114e77b98..f36962b7b5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3482,7 +3482,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_merge_bases;\n-\tstruct tree *mrtree;\n+\tstruct tree *result_tree;\n \tint clean;\n \n \tif (show(opt, 4)) {\n@@ -3552,14 +3552,15 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n \t\t\t\t\t\t\t  merged_merge_bases),\n-\t\t\t\t     &mrtree);\n+\t\t\t\t     &result_tree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\n \t}\n \n \tif (opt->call_depth) {\n-\t\t*result = make_virtual_commit(opt->repo, mrtree, \"merged tree\");\n+\t\t*result = make_virtual_commit(opt->repo, result_tree,\n+\t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379366","messageId":"20190726155258.28561-12-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 11/20] merge-recursive: rename merge_options argument to opt in header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:49Z","receivedAt":"2019-07-26T15:53:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 259ccb6cc324 (\"merge-recursive: rename merge_options argument\nfrom 'o' to 'opt'\", 2019-04-05), I renamed a bunch of function\narguments in merge-recursive.c, but forgot to make that same change to\nmerge-recursive.h.  Make the two match.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.h | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c7ba8d0a71..c201de0fc3 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -64,10 +64,10 @@ struct collision_entry {\n \tunsigned reported_already:1;\n };\n \n-static inline int merge_detect_rename(struct merge_options *o)\n+static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn o->merge_detect_rename >= 0 ? o->merge_detect_rename :\n-\t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n }\n \n /*\n@@ -81,7 +81,7 @@ static inline int merge_detect_rename(struct merge_options *o)\n  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n  *       copy if you need it.\n  */\n-int merge_recursive(struct merge_options *o,\n+int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *merge_bases,\n@@ -91,7 +91,7 @@ int merge_recursive(struct merge_options *o,\n  * rename-detecting three-way merge, no recursion; result of merge is written\n  * to opt->repo->index.\n  */\n-int merge_trees(struct merge_options *o,\n+int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *merge_base);\n@@ -100,16 +100,16 @@ int merge_trees(struct merge_options *o,\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n  * virtual commits and call merge_recursive() proper.\n  */\n-int merge_recursive_generic(struct merge_options *o,\n+int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *o,\n+void init_merge_options(struct merge_options *opt,\n \t\t\tstruct repository *repo);\n \n-int parse_merge_opt(struct merge_options *out, const char *s);\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n \n #endif\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379367","messageId":"20190726155258.28561-10-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 09/20] merge-recursive: use common name for ancestors/common/base_list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:47Z","receivedAt":"2019-07-26T15:53:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees(), merge_recursive(), and merge_recursive_generic() in\ntheir function headers used four different names for the merge base or\nlist of merge bases they were passed:\n  * 'common'\n  * 'ancestors'\n  * 'ca'\n  * 'base_list'\nThey were able to refer to it four different ways instead of only three\nby using a different name in the signature for the .c file than the .h\nfile.  Change all of these to 'merge_base' or 'merge_bases'.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 68 ++++++++++++++++++++++++-----------------------\n merge-recursive.h | 14 +++++-----\n 2 files changed, 42 insertions(+), 40 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e81dec8f1f..8114e77b98 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3356,24 +3356,26 @@ static int process_entry(struct merge_options *opt,\n static int merge_trees_internal(struct merge_options *opt,\n \t\t\t\tstruct tree *head,\n \t\t\t\tstruct tree *merge,\n-\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree *merge_base,\n \t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n \n \tif (opt->subtree_shift) {\n-\t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n-\t\tcommon = shift_tree_object(opt->repo, head, common, opt->subtree_shift);\n+\t\tmerge = shift_tree_object(opt->repo, head, merge,\n+\t\t\t\t\t  opt->subtree_shift);\n+\t\tmerge_base = shift_tree_object(opt->repo, head, merge_base,\n+\t\t\t\t\t       opt->subtree_shift);\n \t}\n \n-\tif (oid_eq(&common->object.oid, &merge->object.oid)) {\n+\tif (oid_eq(&merge_base->object.oid, &merge->object.oid)) {\n \t\toutput(opt, 0, _(\"Already up to date!\"));\n \t\t*result = head;\n \t\treturn 1;\n \t}\n \n-\tcode = unpack_trees_start(opt, common, head, merge);\n+\tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(opt, 4) || opt->call_depth)\n@@ -3401,7 +3403,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tget_files_dirs(opt, merge);\n \n \t\tentries = get_unmerged(opt->repo->index);\n-\t\tclean = detect_and_process_renames(opt, common, head, merge,\n+\t\tclean = detect_and_process_renames(opt, merge_base, head, merge,\n \t\t\t\t\t\t   entries, &re_info);\n \t\trecord_df_conflict_files(opt, entries);\n \t\tif (clean < 0)\n@@ -3475,11 +3477,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t    struct commit *h1,\n \t\t\t\t    struct commit *h2,\n-\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit_list *merge_bases,\n \t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n-\tstruct commit *merged_common_ancestors;\n+\tstruct commit *merged_merge_bases;\n \tstruct tree *mrtree;\n \tint clean;\n \n@@ -3489,31 +3491,31 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\toutput_commit_title(opt, h2);\n \t}\n \n-\tif (!ca) {\n-\t\tca = get_merge_bases(h1, h2);\n-\t\tca = reverse_commit_list(ca);\n+\tif (!merge_bases) {\n+\t\tmerge_bases = get_merge_bases(h1, h2);\n+\t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \n \tif (show(opt, 5)) {\n-\t\tunsigned cnt = commit_list_count(ca);\n+\t\tunsigned cnt = commit_list_count(merge_bases);\n \n \t\toutput(opt, 5, Q_(\"found %u common ancestor:\",\n \t\t\t\t\"found %u common ancestors:\", cnt), cnt);\n-\t\tfor (iter = ca; iter; iter = iter->next)\n+\t\tfor (iter = merge_bases; iter; iter = iter->next)\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n-\tmerged_common_ancestors = pop_commit(&ca);\n-\tif (merged_common_ancestors == NULL) {\n+\tmerged_merge_bases = pop_commit(&merge_bases);\n+\tif (merged_merge_bases == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n-\t\t\t\t\t\t\t      tree, \"ancestor\");\n+\t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n+\t\t\t\t\t\t\t \"ancestor\");\n \t}\n \n-\tfor (iter = ca; iter; iter = iter->next) {\n+\tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n \t\topt->call_depth++;\n \t\t/*\n@@ -3529,14 +3531,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n-\t\t\t\t    NULL, &merged_common_ancestors) < 0)\n+\t\tif (merge_recursive_internal(opt, merged_merge_bases, iter->item,\n+\t\t\t\t\t     NULL, &merged_merge_bases) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->call_depth--;\n \n-\t\tif (!merged_common_ancestors)\n+\t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n@@ -3549,7 +3551,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &mrtree);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n@@ -3593,14 +3595,14 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common)\n+\t\tstruct tree *merge_base)\n {\n \tint clean;\n \tstruct tree *ignored;\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n+\tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n \tmerge_finalize(opt);\n \n \treturn clean;\n@@ -3609,14 +3611,14 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result)\n {\n \tint clean;\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n-\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n \tmerge_finalize(opt);\n \n \treturn clean;\n@@ -3644,8 +3646,8 @@ static struct commit *get_ref(struct repository *repo,\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_base_list,\n-\t\t\t    const struct object_id **base_list,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result)\n {\n \tint clean;\n@@ -3654,14 +3656,14 @@ int merge_recursive_generic(struct merge_options *opt,\n \tstruct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);\n \tstruct commit_list *ca = NULL;\n \n-\tif (base_list) {\n+\tif (merge_bases) {\n \t\tint i;\n-\t\tfor (i = 0; i < num_base_list; ++i) {\n+\t\tfor (i = 0; i < num_merge_bases; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n-\t\t\t\t\t     oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, merge_bases[i],\n+\t\t\t\t\t     oid_to_hex(merge_bases[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n-\t\t\t\t\t   oid_to_hex(base_list[i]));\n+\t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t}\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex b3394502c7..c7ba8d0a71 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -77,14 +77,14 @@ static inline int merge_detect_rename(struct merge_options *o)\n  *\n  * NOTE: empirically, about a decade ago it was determined that with more\n  *       than two merge bases, optimal behavior was found when the\n- *       ancestors were passed in the order of oldest merge base to newest\n- *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n- *       you need it.\n+ *       merge_bases were passed in the order of oldest commit to newest\n+ *       commit.  Also, merge_bases will be consumed (emptied) so make a\n+ *       copy if you need it.\n  */\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ancestors,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /*\n@@ -94,7 +94,7 @@ int merge_recursive(struct merge_options *o,\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common);\n+\t\tstruct tree *merge_base);\n \n /*\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n@@ -103,8 +103,8 @@ int merge_trees(struct merge_options *o,\n int merge_recursive_generic(struct merge_options *o,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_ca,\n-\t\t\t    const struct object_id **ca,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n void init_merge_options(struct merge_options *o,\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379368","messageId":"20190726155258.28561-15-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 14/20] merge-recursive: comment and reorder the merge_options fields","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:52Z","receivedAt":"2019-07-26T15:53:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The merge_options struct had lots of fields, making it a little\nimposing, but the options naturally fall into multiple different groups.\nGrouping similar options and adding a comment or two makes it easier to\nread, easier for new folks to figure out which options are related, and\nthus easier for them to find the options they need.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 16 +++++++++++-----\n merge-recursive.h | 34 +++++++++++++++++++++++-----------\n 2 files changed, 34 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex bafac63c45..a8e4a6a531 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3743,21 +3743,27 @@ void init_merge_options(struct merge_options *opt,\n {\n \tconst char *merge_verbosity;\n \tmemset(opt, 0, sizeof(struct merge_options));\n+\n \topt->repo = repo;\n+\n+\topt->detect_renames = -1;\n+\topt->detect_directory_renames = 1;\n+\topt->rename_limit = -1;\n+\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->rename_limit = -1;\n+\tstrbuf_init(&opt->obuf, 0);\n+\n \topt->renormalize = 0;\n-\topt->detect_renames = -1;\n-\topt->detect_directory_renames = 1;\n+\n+\tstring_list_init(&opt->df_conflict_file_set, 1);\n+\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\n \t\topt->verbosity = strtol(merge_verbosity, NULL, 10);\n \tif (opt->verbosity >= 5)\n \t\topt->buffer_output = 0;\n-\tstrbuf_init(&opt->obuf, 0);\n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n }\n \n int parse_merge_opt(struct merge_options *opt, const char *s)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex b92a9ebce7..b6d420960b 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -9,32 +9,44 @@ struct commit;\n struct repository;\n \n struct merge_options {\n+\tstruct repository *repo;\n+\n+\t/* ref names used in console messages and conflict markers */\n \tconst char *ancestor;\n \tconst char *branch1;\n \tconst char *branch2;\n+\n+\t/* rename related options */\n+\tint detect_renames;\n+\tint detect_directory_renames;\n+\tint rename_limit;\n+\tint rename_score;\n+\tint show_rename_progress;\n+\n+\t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n+\tlong xdl_opts;\n \tenum {\n \t\tMERGE_RECURSIVE_NORMAL = 0,\n \t\tMERGE_RECURSIVE_OURS,\n \t\tMERGE_RECURSIVE_THEIRS\n \t} recursive_variant;\n-\tconst char *subtree_shift;\n+\n+\t/* console output related options */\n+\tint verbosity;\n \tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n+\tstruct strbuf obuf;     /* output buffer */\n+\n+\t/* miscellaneous control options */\n+\tconst char *subtree_shift;\n \tunsigned renormalize : 1;\n-\tlong xdl_opts;\n-\tint verbosity;\n-\tint detect_directory_renames;\n-\tint detect_renames;\n-\tint rename_limit;\n-\tint rename_score;\n-\tint needed_rename_limit;\n-\tint show_rename_progress;\n+\n+\t/* internal fields used by the implementation (do NOT set these) */\n \tint call_depth;\n-\tstruct strbuf obuf;\n+\tint needed_rename_limit;\n \tstruct hashmap current_file_dir_set;\n \tstruct string_list df_conflict_file_set;\n \tstruct unpack_trees_options unpack_opts;\n \tstruct index_state orig_index;\n-\tstruct repository *repo;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379369","messageId":"20190726155258.28561-14-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 13/20] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:51Z","receivedAt":"2019-07-26T15:53:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We provided users with the ability to state whether they wanted rename\ndetection, and to put a limit on how much CPU would be spent.  Both of\nthese fields had multiple configuration parameters for setting them,\nwith one being a fallback and the other being an override.  However,\ninstead of implementing the logic for how to combine the multiple\nsource locations into the appropriate setting at config loading time,\nwe loaded and tracked both values and then made the code combine them\nevery time it wanted to check the overall value.  This had a few\nminor drawbacks:\n  * it seems more complicated than necessary\n  * it runs the risk of people using the independent settings in the\n    future and breaking the intent of how the options are used\n    together\n  * it makes merge_options more complicated than necessary for other\n    potential users of the API\n\nFix these problems by moving the logic for combining the pairs of\noptions into a single value; make it apply at time-of-config-loading\ninstead of each-time-of-use.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 27 +++++++++++----------------\n merge-recursive.h |  6 ++----\n 2 files changed, 13 insertions(+), 20 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 6820578258..bafac63c45 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -385,8 +385,7 @@ static int add_cacheinfo(struct merge_options *opt,\n \n static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+\treturn (opt->detect_renames != -1) ? opt->detect_renames : 1;\n }\n \n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n@@ -1885,9 +1884,7 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \t */\n \tif (opts.detect_rename > DIFF_DETECT_RENAME)\n \t\topts.detect_rename = DIFF_DETECT_RENAME;\n-\topts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n-\t\t\t    opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n-\t\t\t    1000;\n+\topts.rename_limit = (opt->rename_limit != -1) ? opt->rename_limit : 1000;\n \topts.rename_score = opt->rename_score;\n \topts.show_rename_progress = opt->show_rename_progress;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -3719,14 +3716,14 @@ static void merge_recursive_config(struct merge_options *opt)\n {\n \tchar *value = NULL;\n \tgit_config_get_int(\"merge.verbosity\", &opt->verbosity);\n-\tgit_config_get_int(\"diff.renamelimit\", &opt->diff_rename_limit);\n-\tgit_config_get_int(\"merge.renamelimit\", &opt->merge_rename_limit);\n+\tgit_config_get_int(\"diff.renamelimit\", &opt->rename_limit);\n+\tgit_config_get_int(\"merge.renamelimit\", &opt->rename_limit);\n \tif (!git_config_get_string(\"diff.renames\", &value)) {\n-\t\topt->diff_detect_rename = git_config_rename(\"diff.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"diff.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.renames\", &value)) {\n-\t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n@@ -3749,11 +3746,9 @@ void init_merge_options(struct merge_options *opt,\n \topt->repo = repo;\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->diff_rename_limit = -1;\n-\topt->merge_rename_limit = -1;\n+\topt->rename_limit = -1;\n \topt->renormalize = 0;\n-\topt->diff_detect_rename = -1;\n-\topt->merge_detect_rename = -1;\n+\topt->detect_renames = -1;\n \topt->detect_directory_renames = 1;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n@@ -3805,16 +3800,16 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \telse if (!strcmp(s, \"no-renormalize\"))\n \t\topt->renormalize = 0;\n \telse if (!strcmp(s, \"no-renames\"))\n-\t\topt->merge_detect_rename = 0;\n+\t\topt->detect_renames = 0;\n \telse if (!strcmp(s, \"find-renames\")) {\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t\topt->rename_score = 0;\n \t}\n \telse if (skip_prefix(s, \"find-renames=\", &arg) ||\n \t\t skip_prefix(s, \"rename-threshold=\", &arg)) {\n \t\tif ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)\n \t\t\treturn -1;\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t}\n \t/*\n \t * Please update $__git_merge_strategy_options in\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex ebec855a65..b92a9ebce7 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -23,10 +23,8 @@ struct merge_options {\n \tlong xdl_opts;\n \tint verbosity;\n \tint detect_directory_renames;\n-\tint diff_detect_rename;\n-\tint merge_detect_rename;\n-\tint diff_rename_limit;\n-\tint merge_rename_limit;\n+\tint detect_renames;\n+\tint rename_limit;\n \tint rename_score;\n \tint needed_rename_limit;\n \tint show_rename_progress;\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379370","messageId":"20190726155258.28561-16-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 15/20] merge-recursive: avoid losing output and leaking memory holding that output","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:53Z","receivedAt":"2019-07-26T15:53:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If opt->buffer_output is less than 2, then merge_trees(),\nmerge_recursive(), and merge_recursive_generic() are all supposed to\nflush the opt->obuf output buffer to stdout and release any memory it\nholds.  merge_trees() did not do this.  Move the logic that handles this\nfor merge_recursive_internal() to merge_finalize() so that all three\nmethods handle this requirement.\n\nNote that this bug didn't cause any problems currently, because there\nare only two callers of merge_trees() right now (a git grep for\n'merge_trees(' is misleading because builtin/merge-tree.c also defines a\n'merge_tree' function that is unrelated), and only one of those is\ncalled with buffer_output less than 2 (builtin/checkout.c), but it set\nopt->verbosity to 0, for which there is only currently one non-error\nmessage that would be shown: \"Already up to date!\".  However, that one\nmessage can only occur when the merge is utterly trivial (the merge base\ntree exactly matches the merge tree), and builtin/checkout.c already\nattempts a trivial merge via unpack_trees() before falling back to\nmerge_trees().\n\nAlso, if opt->buffer_output is 2, then the caller is responsible to\nhandle showing any output in opt->obuf and for free'ing it.  This\nrequirement might be easy to overlook, so add a comment to\nmerge-recursive.h pointing it out.  (There are currently two callers\nthat set buffer_output to 2, both in sequencer.c, and both of which\nhandle this correctly.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 6 +++---\n merge-recursive.h | 3 ++-\n 2 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a8e4a6a531..49c7de0379 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3592,9 +3592,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-\tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n-\t\tstrbuf_release(&opt->obuf);\n \treturn clean;\n }\n \n@@ -3616,6 +3613,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n+\tflush_output(opt);\n+\tif (!opt->call_depth && opt->buffer_output < 2)\n+\t\tstrbuf_release(&opt->obuf);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opt->needed_rename_limit, 0);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex b6d420960b..12249258ef 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -34,7 +34,8 @@ struct merge_options {\n \t/* console output related options */\n \tint verbosity;\n \tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n-\tstruct strbuf obuf;     /* output buffer */\n+\tstruct strbuf obuf;     /* output buffer; if buffer_output == 2, caller\n+\t\t\t\t * must handle and call strbuf_release */\n \n \t/* miscellaneous control options */\n \tconst char *subtree_shift;\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379371","messageId":"20190726155258.28561-13-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 12/20] merge-recursive: move some definitions around to clean up the header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:50Z","receivedAt":"2019-07-26T15:53:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code changes (view this with diff --color-moved), but\na few small code cleanups:\n  * Move structs and an inline function only used by merge-recursive.c\n    into merge-recursive.c\n  * Re-order function declarations to be more logical\n  * Add or fix some explanatory comments\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 31 +++++++++++++++++\n merge-recursive.h | 87 +++++++++++++++++++++++------------------------\n 2 files changed, 73 insertions(+), 45 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f36962b7b5..6820578258 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -54,6 +54,24 @@ static unsigned int path_hash(const char *path)\n \treturn ignore_case ? strihash(path) : strhash(path);\n }\n \n+/*\n+ * For dir_rename_entry, directory names are stored as a full path from the\n+ * toplevel of the repository and do not include a trailing '/'.  Also:\n+ *\n+ *   dir:                original name of directory being renamed\n+ *   non_unique_new_dir: if true, could not determine new_dir\n+ *   new_dir:            final name of directory being renamed\n+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n+ *                       in get_directory_renames() for details\n+ */\n+struct dir_rename_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *dir;\n+\tunsigned non_unique_new_dir:1;\n+\tstruct strbuf new_dir;\n+\tstruct string_list possible_new_dirs;\n+};\n+\n static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t      char *dir)\n {\n@@ -92,6 +110,13 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,\n \tstring_list_init(&entry->possible_new_dirs, 0);\n }\n \n+struct collision_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *target_file;\n+\tstruct string_list source_files;\n+\tunsigned reported_already:1;\n+};\n+\n static struct collision_entry *collision_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t    char *target_file)\n {\n@@ -358,6 +383,12 @@ static int add_cacheinfo(struct merge_options *opt,\n \treturn ret;\n }\n \n+static inline int merge_detect_rename(struct merge_options *opt)\n+{\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+}\n+\n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n {\n \tparse_tree(tree);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c201de0fc3..ebec855a65 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -39,47 +39,50 @@ struct merge_options {\n \tstruct repository *repo;\n };\n \n+void init_merge_options(struct merge_options *opt, struct repository *repo);\n+\n+/* parse the option in s and update the relevant field of opt */\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n+\n /*\n- * For dir_rename_entry, directory names are stored as a full path from the\n- * toplevel of the repository and do not include a trailing '/'.  Also:\n- *\n- *   dir:                original name of directory being renamed\n- *   non_unique_new_dir: if true, could not determine new_dir\n- *   new_dir:            final name of directory being renamed\n- *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n- *                       in get_directory_renames() for details\n+ * RETURN VALUES: All the merge_* functions below return a value as follows:\n+ *   > 0     Merge was clean\n+ *   = 0     Merge had conflicts\n+ *   < 0     Merge hit an unexpected and unrecoverable problem (e.g. disk\n+ *             full) and aborted merge part-way through.\n  */\n-struct dir_rename_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *dir;\n-\tunsigned non_unique_new_dir:1;\n-\tstruct strbuf new_dir;\n-\tstruct string_list possible_new_dirs;\n-};\n-\n-struct collision_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *target_file;\n-\tstruct string_list source_files;\n-\tunsigned reported_already:1;\n-};\n \n-static inline int merge_detect_rename(struct merge_options *opt)\n-{\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n-}\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - No commit is created\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is not updated\n+ *   - The working tree is updated with results of the merge\n+ */\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *merge_base);\n \n /*\n  * merge_recursive is like merge_trees() but with recursive ancestor\n- * consolidation, and when successful, it creates an actual commit\n- * and writes its address to *result.\n+ * consolidation and, if the commit is clean, creation of a commit.\n  *\n  * NOTE: empirically, about a decade ago it was determined that with more\n  *       than two merge bases, optimal behavior was found when the\n  *       merge_bases were passed in the order of oldest commit to newest\n  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n  *       copy if you need it.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - If merge is clean, a commit is created and its address written to *result\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is not updated\n+ *   - The working tree is updated with results of the merge\n  */\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n@@ -88,17 +91,16 @@ int merge_recursive(struct merge_options *opt,\n \t\t    struct commit **result);\n \n /*\n- * rename-detecting three-way merge, no recursion; result of merge is written\n- * to opt->repo->index.\n- */\n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *merge_base);\n-\n-/*\n- * \"git-merge-recursive\" can be fed trees; wrap them into\n- * virtual commits and call merge_recursive() proper.\n+ * merge_recursive_generic can operate on trees instead of commits, by\n+ * wrapping the trees into virtual commits, and calling merge_recursive().\n+ * It also writes out the in-memory index to disk if the merge is successful.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - If merge is clean, a commit is created and its address written to *result\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is updated\n+ *   - The working tree is updated with results of the merge\n  */\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n@@ -107,9 +109,4 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *opt,\n-\t\t\tstruct repository *repo);\n-\n-int parse_merge_opt(struct merge_options *opt, const char *s);\n-\n #endif\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379372","messageId":"20190726155258.28561-17-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 16/20] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:54Z","receivedAt":"2019-07-26T15:53:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_options has several internal fields that should not be set or read\nby external callers.  This just complicates the API.  Move them into an\nopaque merge_options_internal struct that is defined only in\nmerge-recursive.c and keep these out of merge-recursive.h.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 184 ++++++++++++++++++++++++----------------------\n merge-recursive.h |  17 ++---\n 2 files changed, 105 insertions(+), 96 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 49c7de0379..ba635ed753 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -29,6 +29,15 @@\n #include \"revision.h\"\n #include \"commit-reach.h\"\n \n+struct merge_options_internal {\n+\tint call_depth;\n+\tint needed_rename_limit;\n+\tstruct hashmap current_file_dir_set;\n+\tstruct string_list df_conflict_file_set;\n+\tstruct unpack_trees_options unpack_opts;\n+\tstruct index_state orig_index;\n+};\n+\n struct path_hashmap_entry {\n \tstruct hashmap_entry e;\n \tchar path[FLEX_ARRAY];\n@@ -309,7 +318,8 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \n static int show(struct merge_options *opt, int v)\n {\n-\treturn (!opt->call_depth && opt->verbosity >= v) || opt->verbosity >= 5;\n+\treturn (!opt->priv->call_depth && opt->verbosity >= v) ||\n+\t\topt->verbosity >= 5;\n }\n \n __attribute__((format (printf, 3, 4)))\n@@ -320,7 +330,7 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)\n \tif (!show(opt, v))\n \t\treturn;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \n \tva_start(ap, fmt);\n \tstrbuf_vaddf(&opt->obuf, fmt, ap);\n@@ -335,7 +345,7 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit\n {\n \tstruct merge_remote_desc *desc;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \tdesc = merge_remote_util(commit);\n \tif (desc)\n \t\tstrbuf_addf(&opt->obuf, \"virtual %s\\n\", desc->name);\n@@ -403,43 +413,43 @@ static int unpack_trees_start(struct merge_options *opt,\n \tstruct tree_desc t[3];\n \tstruct index_state tmp_index = { NULL };\n \n-\tmemset(&opt->unpack_opts, 0, sizeof(opt->unpack_opts));\n-\tif (opt->call_depth)\n-\t\topt->unpack_opts.index_only = 1;\n+\tmemset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));\n+\tif (opt->priv->call_depth)\n+\t\topt->priv->unpack_opts.index_only = 1;\n \telse\n-\t\topt->unpack_opts.update = 1;\n-\topt->unpack_opts.merge = 1;\n-\topt->unpack_opts.head_idx = 2;\n-\topt->unpack_opts.fn = threeway_merge;\n-\topt->unpack_opts.src_index = opt->repo->index;\n-\topt->unpack_opts.dst_index = &tmp_index;\n-\topt->unpack_opts.aggressive = !merge_detect_rename(opt);\n-\tsetup_unpack_trees_porcelain(&opt->unpack_opts, \"merge\");\n+\t\topt->priv->unpack_opts.update = 1;\n+\topt->priv->unpack_opts.merge = 1;\n+\topt->priv->unpack_opts.head_idx = 2;\n+\topt->priv->unpack_opts.fn = threeway_merge;\n+\topt->priv->unpack_opts.src_index = opt->repo->index;\n+\topt->priv->unpack_opts.dst_index = &tmp_index;\n+\topt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);\n+\tsetup_unpack_trees_porcelain(&opt->priv->unpack_opts, \"merge\");\n \n \tinit_tree_desc_from_tree(t+0, common);\n \tinit_tree_desc_from_tree(t+1, head);\n \tinit_tree_desc_from_tree(t+2, merge);\n \n-\trc = unpack_trees(3, t, &opt->unpack_opts);\n+\trc = unpack_trees(3, t, &opt->priv->unpack_opts);\n \tcache_tree_free(&opt->repo->index->cache_tree);\n \n \t/*\n-\t * Update opt->repo->index to match the new results, AFTER saving a copy\n-\t * in opt->orig_index.  Update src_index to point to the saved copy.\n-\t * (verify_uptodate() checks src_index, and the original index is\n-\t * the one that had the necessary modification timestamps.)\n+\t * Update opt->repo->index to match the new results, AFTER saving a\n+\t * copy in opt->priv->orig_index.  Update src_index to point to the\n+\t * saved copy.  (verify_uptodate() checks src_index, and the original\n+\t * index is the one that had the necessary modification timestamps.)\n \t */\n-\topt->orig_index = *opt->repo->index;\n+\topt->priv->orig_index = *opt->repo->index;\n \t*opt->repo->index = tmp_index;\n-\topt->unpack_opts.src_index = &opt->orig_index;\n+\topt->priv->unpack_opts.src_index = &opt->priv->orig_index;\n \n \treturn rc;\n }\n \n static void unpack_trees_finish(struct merge_options *opt)\n {\n-\tdiscard_index(&opt->orig_index);\n-\tclear_unpack_trees_porcelain(&opt->unpack_opts);\n+\tdiscard_index(&opt->priv->orig_index);\n+\tclear_unpack_trees_porcelain(&opt->priv->unpack_opts);\n }\n \n static int save_files_dirs(const struct object_id *oid,\n@@ -454,7 +464,7 @@ static int save_files_dirs(const struct object_id *oid,\n \n \tFLEX_ALLOC_MEM(entry, path, base->buf, base->len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \n \tstrbuf_setlen(base, baselen);\n \treturn (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);\n@@ -585,7 +595,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t * If we're merging merge-bases, we don't want to bother with\n \t * any working directory changes.\n \t */\n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn;\n \n \t/* Ensure D/F conflicts are adjacent in the entries list. */\n@@ -597,7 +607,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \tdf_sorted_entries.cmp = string_list_df_name_compare;\n \tstring_list_sort(&df_sorted_entries);\n \n-\tstring_list_clear(&opt->df_conflict_file_set, 1);\n+\tstring_list_clear(&opt->priv->df_conflict_file_set, 1);\n \tfor (i = 0; i < df_sorted_entries.nr; i++) {\n \t\tconst char *path = df_sorted_entries.items[i].string;\n \t\tint len = strlen(path);\n@@ -613,7 +623,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t\t    len > last_len &&\n \t\t    memcmp(path, last_file, last_len) == 0 &&\n \t\t    path[last_len] == '/') {\n-\t\t\tstring_list_insert(&opt->df_conflict_file_set, last_file);\n+\t\t\tstring_list_insert(&opt->priv->df_conflict_file_set, last_file);\n \t\t}\n \n \t\t/*\n@@ -680,8 +690,8 @@ static void update_entry(struct stage_data *entry,\n static int remove_file(struct merge_options *opt, int clean,\n \t\t       const char *path, int no_wd)\n {\n-\tint update_cache = opt->call_depth || clean;\n-\tint update_working_directory = !opt->call_depth && !no_wd;\n+\tint update_cache = opt->priv->call_depth || clean;\n+\tint update_working_directory = !opt->priv->call_depth && !no_wd;\n \n \tif (update_cache) {\n \t\tif (remove_file_from_index(opt->repo->index, path))\n@@ -724,16 +734,16 @@ static char *unique_path(struct merge_options *opt,\n \tadd_flattened_path(&newpath, branch);\n \n \tbase_len = newpath.len;\n-\twhile (hashmap_get_from_hash(&opt->current_file_dir_set,\n+\twhile (hashmap_get_from_hash(&opt->priv->current_file_dir_set,\n \t\t\t\t     path_hash(newpath.buf), newpath.buf) ||\n-\t       (!opt->call_depth && file_exists(newpath.buf))) {\n+\t       (!opt->priv->call_depth && file_exists(newpath.buf))) {\n \t\tstrbuf_setlen(&newpath, base_len);\n \t\tstrbuf_addf(&newpath, \"_%d\", suffix++);\n \t}\n \n \tFLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \treturn strbuf_detach(&newpath, NULL);\n }\n \n@@ -775,7 +785,7 @@ static int dir_in_way(struct index_state *istate, const char *path,\n static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\t\t\t   const struct diff_filespec *blob)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \tstruct cache_entry *ce;\n \n \tif (0 > pos)\n@@ -783,7 +793,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\treturn 0;\n \n \t/* See if the file we were tracking before matches */\n-\tce = opt->orig_index.cache[pos];\n+\tce = opt->priv->orig_index.cache[pos];\n \treturn (oid_eq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);\n }\n \n@@ -792,7 +802,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n  */\n static int was_tracked(struct merge_options *opt, const char *path)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \n \tif (0 <= pos)\n \t\t/* we were tracking this path before the merge */\n@@ -849,12 +859,12 @@ static int was_dirty(struct merge_options *opt, const char *path)\n \tstruct cache_entry *ce;\n \tint dirty = 1;\n \n-\tif (opt->call_depth || !was_tracked(opt, path))\n+\tif (opt->priv->call_depth || !was_tracked(opt, path))\n \t\treturn !dirty;\n \n-\tce = index_file_exists(opt->unpack_opts.src_index,\n+\tce = index_file_exists(opt->priv->unpack_opts.src_index,\n \t\t\t       path, strlen(path), ignore_case);\n-\tdirty = verify_uptodate(ce, &opt->unpack_opts) != 0;\n+\tdirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;\n \treturn dirty;\n }\n \n@@ -864,8 +874,8 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \tconst char *msg = _(\"failed to create path '%s'%s\");\n \n \t/* Unlink any D/F conflict files that are in the way */\n-\tfor (i = 0; i < opt->df_conflict_file_set.nr; i++) {\n-\t\tconst char *df_path = opt->df_conflict_file_set.items[i].string;\n+\tfor (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {\n+\t\tconst char *df_path = opt->priv->df_conflict_file_set.items[i].string;\n \t\tsize_t pathlen = strlen(path);\n \t\tsize_t df_pathlen = strlen(df_path);\n \t\tif (df_pathlen < pathlen &&\n@@ -875,7 +885,7 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \t\t\t       _(\"Removing %s to make room for subdirectory\\n\"),\n \t\t\t       df_path);\n \t\t\tunlink(df_path);\n-\t\t\tunsorted_string_list_delete_item(&opt->df_conflict_file_set,\n+\t\t\tunsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,\n \t\t\t\t\t\t\t i, 0);\n \t\t\tbreak;\n \t\t}\n@@ -916,7 +926,7 @@ static int update_file_flags(struct merge_options *opt,\n {\n \tint ret = 0;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\tupdate_wd = 0;\n \n \tif (update_wd) {\n@@ -1001,7 +1011,7 @@ static int update_file(struct merge_options *opt,\n \t\t       const char *path)\n {\n \treturn update_file_flags(opt, contents, path,\n-\t\t\t\t opt->call_depth || clean, !opt->call_depth);\n+\t\t\t\t opt->priv->call_depth || clean, !opt->priv->call_depth);\n }\n \n /* Low level file merging, update and removal */\n@@ -1030,7 +1040,7 @@ static int merge_3way(struct merge_options *opt,\n \tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = opt->xdl_opts;\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\tll_opts.virtual_ancestor = 1;\n \t\tll_opts.variant = 0;\n \t} else {\n@@ -1164,7 +1174,7 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct object_array merges;\n \n \tint i;\n-\tint search = !opt->call_depth;\n+\tint search = !opt->priv->call_depth;\n \n \t/* store a in result in case we fail */\n \toidcpy(result, a);\n@@ -1385,7 +1395,7 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \tint mark_conflicted = (opt->detect_directory_renames == 1);\n \tassert(ren->dir_rename_original_dest);\n \n-\tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n+\tif (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) {\n \t\tmark_conflicted = 1;\n \t\tfile_path = unique_path(opt, dest->path, ren->branch);\n \t\toutput(opt, 1, _(\"Error: Refusing to lose untracked file at %s; \"\n@@ -1428,12 +1438,12 @@ static int handle_change_delete(struct merge_options *opt,\n \tconst char *update_path = path;\n \tint ret = 0;\n \n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0) ||\n-\t    (!opt->call_depth && would_lose_untracked(opt, path))) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) ||\n+\t    (!opt->priv->call_depth && would_lose_untracked(opt, path))) {\n \t\tupdate_path = alt_path = unique_path(opt, path, change_branch);\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * We cannot arbitrarily accept either a_sha or b_sha as\n \t\t * correct; since there is no true \"middle point\" between\n@@ -1508,14 +1518,14 @@ static int handle_rename_delete(struct merge_options *opt,\n \t\t\t\t     opt->branch2 : opt->branch1);\n \n \tif (handle_change_delete(opt,\n-\t\t\t\t opt->call_depth ? orig->path : dest->path,\n-\t\t\t\t opt->call_depth ? NULL : orig->path,\n+\t\t\t\t opt->priv->call_depth ? orig->path : dest->path,\n+\t\t\t\t opt->priv->call_depth ? NULL : orig->path,\n \t\t\t\t orig, dest,\n \t\t\t\t rename_branch, delete_branch,\n \t\t\t\t _(\"rename\"), _(\"renamed\")))\n \t\treturn -1;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn remove_file_from_index(opt->repo->index, dest->path);\n \telse\n \t\treturn update_stages(opt, dest->path, NULL,\n@@ -1552,7 +1562,7 @@ static int handle_file_collision(struct merge_options *opt,\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n-\tif (opt->call_depth && (prev_path1 || prev_path2)) {\n+\tif (opt->priv->call_depth && (prev_path1 || prev_path2)) {\n \t\t/* Put first file (a->oid, a->mode) in its original spot */\n \t\tif (prev_path1) {\n \t\t\tif (update_file(opt, 1, a, prev_path1))\n@@ -1581,10 +1591,10 @@ static int handle_file_collision(struct merge_options *opt,\n \t/* Remove rename sources if rename/add or rename/rename(2to1) */\n \tif (prev_path1)\n \t\tremove_file(opt, 1, prev_path1,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path1));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path1));\n \tif (prev_path2)\n \t\tremove_file(opt, 1, prev_path2,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path2));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path2));\n \n \t/*\n \t * Remove the collision path, if it wouldn't cause dirty contents\n@@ -1626,12 +1636,12 @@ static int handle_file_collision(struct merge_options *opt,\n \tnull.mode = 0;\n \n \tif (merge_mode_and_contents(opt, &null, a, b, collide_path,\n-\t\t\t\t    branch1, branch2, opt->call_depth * 2, &mfi))\n+\t\t\t\t    branch1, branch2, opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tmfi.clean &= !alt_path;\n \tif (update_file(opt, mfi.clean, &mfi.blob, update_path))\n \t\treturn -1;\n-\tif (!mfi.clean && !opt->call_depth &&\n+\tif (!mfi.clean && !opt->priv->call_depth &&\n \t    update_stages(opt, collide_path, NULL, a, b))\n \t\treturn -1;\n \tfree(alt_path);\n@@ -1671,7 +1681,7 @@ static int handle_rename_add(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[other_stage],\n \t\t\t\t    prev_path_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(prev_path_desc);\n \n@@ -1689,7 +1699,7 @@ static char *find_path_for_conflict(struct merge_options *opt,\n \t\t\t\t    const char *branch2)\n {\n \tchar *new_path = NULL;\n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0)) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) {\n \t\tnew_path = unique_path(opt, path, branch1);\n \t\toutput(opt, 1, _(\"%s is a directory in %s adding \"\n \t\t\t       \"as %s instead\"),\n@@ -1720,17 +1730,17 @@ static int handle_rename_rename_1to2(struct merge_options *opt,\n \t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\"),\n \t       o->path, a->path, ci->ren1->branch,\n \t       o->path, b->path, ci->ren2->branch,\n-\t       opt->call_depth ? _(\" (left unresolved)\") : \"\");\n+\t       opt->priv->call_depth ? _(\" (left unresolved)\") : \"\");\n \n \tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n \t\t\t    a->path, b->path, o->path);\n \tif (merge_mode_and_contents(opt, o, a, b, path_desc,\n \t\t\t\t    ci->ren1->branch, ci->ren2->branch,\n-\t\t\t\t    opt->call_depth * 2, &mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(path_desc);\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1845,12 +1855,12 @@ static int handle_rename_rename_2to1(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[ostage1],\n \t\t\t\t    path_side_1_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(opt, b,\n \t\t\t\t    &ci->ren2->src_entry->stages[ostage2],\n \t\t\t\t    c2, path_side_2_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -1891,8 +1901,8 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \tdiff_setup_done(&opts);\n \tdiff_tree_oid(&o_tree->object.oid, &tree->object.oid, \"\", &opts);\n \tdiffcore_std(&opts);\n-\tif (opts.needed_rename_limit > opt->needed_rename_limit)\n-\t\topt->needed_rename_limit = opts.needed_rename_limit;\n+\tif (opts.needed_rename_limit > opt->priv->needed_rename_limit)\n+\t\topt->priv->needed_rename_limit = opts.needed_rename_limit;\n \n \tret = xmalloc(sizeof(*ret));\n \t*ret = diff_queued_diff;\n@@ -3022,13 +3032,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\treason = _(\"add/add\");\n \n \tassert(o->path && a->path && b->path);\n-\tif (ci && dir_in_way(opt->repo->index, path, !opt->call_depth,\n+\tif (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth,\n \t\t\t     S_ISGITLINK(ci->ren1->pair->two->mode)))\n \t\tdf_conflict_remains = 1;\n \n \tif (merge_mode_and_contents(opt, o, a, b, path,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    opt->call_depth * 2, mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, mfi))\n \t\treturn -1;\n \n \t/*\n@@ -3044,7 +3054,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \t\toutput(opt, 3, _(\"Skipped %s (merged same as existing)\"), path);\n \t\tif (add_cacheinfo(opt, &mfi->blob, path,\n-\t\t\t\t  0, (!opt->call_depth && !is_dirty), 0))\n+\t\t\t\t  0, (!opt->priv->call_depth && !is_dirty), 0))\n \t\t\treturn -1;\n \t\t/*\n \t\t * However, add_cacheinfo() will delete the old cache entry\n@@ -3052,8 +3062,8 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\t * flag to avoid making the file appear as if it were\n \t\t * deleted by the user.\n \t\t */\n-\t\tpos = index_name_pos(&opt->orig_index, path, strlen(path));\n-\t\tce = opt->orig_index.cache[pos];\n+\t\tpos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n+\t\tce = opt->priv->orig_index.cache[pos];\n \t\tif (ce_skip_worktree(ce)) {\n \t\t\tpos = index_name_pos(opt->repo->index, path, strlen(path));\n \t\t\tce = opt->repo->index->cache[pos];\n@@ -3074,7 +3084,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \tif (df_conflict_remains || is_dirty) {\n \t\tchar *new_path;\n-\t\tif (opt->call_depth) {\n+\t\tif (opt->priv->call_depth) {\n \t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t} else {\n \t\t\tif (!mfi->clean) {\n@@ -3332,7 +3342,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\tconf = _(\"directory/file\");\n \t\t}\n \t\tif (dir_in_way(opt->repo->index, path,\n-\t\t\t       !opt->call_depth && !S_ISGITLINK(a->mode),\n+\t\t\t       !opt->priv->call_depth && !S_ISGITLINK(a->mode),\n \t\t\t       0)) {\n \t\t\tchar *new_path = unique_path(opt, path, add_branch);\n \t\t\tclean_merge = 0;\n@@ -3341,7 +3351,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tif (update_file(opt, 0, contents, new_path))\n \t\t\t\tclean_merge = -1;\n-\t\t\telse if (opt->call_depth)\n+\t\t\telse if (opt->priv->call_depth)\n \t\t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t\tfree(new_path);\n \t\t} else {\n@@ -3406,7 +3416,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n-\t\tif (show(opt, 4) || opt->call_depth)\n+\t\tif (show(opt, 4) || opt->priv->call_depth)\n \t\t\terr(opt, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n@@ -3425,7 +3435,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\thashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp,\n \t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n@@ -3462,7 +3472,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free(&opt->current_file_dir_set, 1);\n+\t\thashmap_free(&opt->priv->current_file_dir_set, 1);\n \n \t\tif (clean < 0) {\n \t\t\tunpack_trees_finish(opt);\n@@ -3474,7 +3484,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\tstruct object_id tree_id;\n \t\tif (write_index_as_tree(&tree_id, opt->repo->index, NULL,\n \t\t\t\t\tWRITE_TREE_FROM_MEMORY, NULL) ||\n@@ -3545,7 +3555,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \n \tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n-\t\topt->call_depth++;\n+\t\topt->priv->call_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n \t\t * with conflict markers. The cleanness flag is\n@@ -3564,14 +3574,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n-\t\topt->call_depth--;\n+\t\topt->priv->call_depth--;\n \n \t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n \tdiscard_index(opt->repo->index);\n-\tif (!opt->call_depth)\n+\tif (!opt->priv->call_depth)\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = \"merged common ancestors\";\n@@ -3586,7 +3596,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\treturn clean;\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t*result = make_virtual_commit(opt->repo, result_tree,\n \t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n@@ -3608,17 +3618,21 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \t\treturn -1;\n \t}\n \n+\topt->priv = xcalloc(1, sizeof(*opt->priv));\n+\tstring_list_init(&opt->priv->df_conflict_file_set, 1);\n \treturn 0;\n }\n \n static void merge_finalize(struct merge_options *opt)\n {\n \tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n+\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0);\n+\tfree(opt->priv);\n+\topt->priv = NULL;\n }\n \n int merge_trees(struct merge_options *opt,\n@@ -3756,8 +3770,6 @@ void init_merge_options(struct merge_options *opt,\n \n \topt->renormalize = 0;\n \n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n-\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 12249258ef..0d5e928832 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -1,13 +1,15 @@\n #ifndef MERGE_RECURSIVE_H\n #define MERGE_RECURSIVE_H\n \n-#include \"string-list.h\"\n-#include \"unpack-trees.h\"\n+#include \"strbuf.h\"\n \n struct commit;\n-\n+struct commit_list;\n+struct object_id;\n struct repository;\n+struct tree;\n \n+struct merge_options_internal;\n struct merge_options {\n \tstruct repository *repo;\n \n@@ -41,13 +43,8 @@ struct merge_options {\n \tconst char *subtree_shift;\n \tunsigned renormalize : 1;\n \n-\t/* internal fields used by the implementation (do NOT set these) */\n-\tint call_depth;\n-\tint needed_rename_limit;\n-\tstruct hashmap current_file_dir_set;\n-\tstruct string_list df_conflict_file_set;\n-\tstruct unpack_trees_options unpack_opts;\n-\tstruct index_state orig_index;\n+\t/* internal fields used by the implementation */\n+\tstruct merge_options_internal *priv;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379373","messageId":"20190726155258.28561-18-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 17/20] merge-recursive: alphabetize include list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:55Z","receivedAt":"2019-07-26T15:53:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Other than cache.h which needs to appear first, and merge-recursive.h\nwhich I want to be second so that we are more likely to notice if\nmerge-recursive.h has any missing includes, the rest of the list is\nlong and easier to look through if it's alphabetical.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 33 +++++++++++++++++----------------\n 1 file changed, 17 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ba635ed753..27e0bdf48c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -4,30 +4,31 @@\n  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006\n  */\n #include \"cache.h\"\n-#include \"config.h\"\n+#include \"merge-recursive.h\"\n+\n #include \"advice.h\"\n-#include \"lockfile.h\"\n-#include \"cache-tree.h\"\n-#include \"object-store.h\"\n-#include \"repository.h\"\n-#include \"commit.h\"\n+#include \"alloc.h\"\n+#include \"attr.h\"\n #include \"blob.h\"\n #include \"builtin.h\"\n-#include \"tree-walk.h\"\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"commit-reach.h\"\n+#include \"config.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"dir.h\"\n+#include \"ll-merge.h\"\n+#include \"lockfile.h\"\n+#include \"object-store.h\"\n+#include \"repository.h\"\n+#include \"revision.h\"\n+#include \"string-list.h\"\n+#include \"submodule.h\"\n #include \"tag.h\"\n-#include \"alloc.h\"\n+#include \"tree-walk.h\"\n #include \"unpack-trees.h\"\n-#include \"string-list.h\"\n #include \"xdiff-interface.h\"\n-#include \"ll-merge.h\"\n-#include \"attr.h\"\n-#include \"merge-recursive.h\"\n-#include \"dir.h\"\n-#include \"submodule.h\"\n-#include \"revision.h\"\n-#include \"commit-reach.h\"\n \n struct merge_options_internal {\n \tint call_depth;\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379374","messageId":"20190726155258.28561-19-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 18/20] merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:56Z","receivedAt":"2019-07-26T15:53:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"I want to implement the same outward facing API as found within\nmerge-recursive.h in a different merge strategy.  However, that makes\nnames like MERGE_RECURSIVE_{NORMAL,OURS,THEIRS} look a little funny;\nrename to MERGE_VARIANT_{NORMAL,OURS,THEIRS}.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 14 +++++++-------\n merge-recursive.h |  6 +++---\n 2 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 27e0bdf48c..49eb50c17b 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1046,10 +1046,10 @@ static int merge_3way(struct merge_options *opt,\n \t\tll_opts.variant = 0;\n \t} else {\n \t\tswitch (opt->recursive_variant) {\n-\t\tcase MERGE_RECURSIVE_OURS:\n+\t\tcase MERGE_VARIANT_OURS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_OURS;\n \t\t\tbreak;\n-\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n \t\t\tbreak;\n \t\tdefault:\n@@ -1359,15 +1359,15 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&b->oid);\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n-\t\t\tcase MERGE_RECURSIVE_NORMAL:\n+\t\t\tcase MERGE_VARIANT_NORMAL:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tif (!oid_eq(&a->oid, &b->oid))\n \t\t\t\t\tresult->clean = 0;\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_OURS:\n+\t\t\tcase MERGE_VARIANT_OURS:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\t\toidcpy(&result->blob.oid, &b->oid);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -3786,9 +3786,9 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \tif (!s || !*s)\n \t\treturn -1;\n \tif (!strcmp(s, \"ours\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_OURS;\n+\t\topt->recursive_variant = MERGE_VARIANT_OURS;\n \telse if (!strcmp(s, \"theirs\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_THEIRS;\n+\t\topt->recursive_variant = MERGE_VARIANT_THEIRS;\n \telse if (!strcmp(s, \"subtree\"))\n \t\topt->subtree_shift = \"\";\n \telse if (skip_prefix(s, \"subtree=\", &arg))\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 0d5e928832..350fb43a01 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -28,9 +28,9 @@ struct merge_options {\n \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n \tlong xdl_opts;\n \tenum {\n-\t\tMERGE_RECURSIVE_NORMAL = 0,\n-\t\tMERGE_RECURSIVE_OURS,\n-\t\tMERGE_RECURSIVE_THEIRS\n+\t\tMERGE_VARIANT_NORMAL = 0,\n+\t\tMERGE_VARIANT_OURS,\n+\t\tMERGE_VARIANT_THEIRS\n \t} recursive_variant;\n \n \t/* console output related options */\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379375","messageId":"20190726155258.28561-21-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 20/20] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:58Z","receivedAt":"2019-07-26T15:53:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 7ca56aa07619 (\"merge-recursive: add a label for ancestor\",\n2010-03-20), a label was added for the '||||||' line to make it have\nthe more informative heading '|||||| merged common ancestors', with\nthe statement:\n\n    It would be nicer to use a more informative label.  Perhaps someone\n    will provide one some day.\n\nThis chosen label was perfectly reasonable when recursiveness kicks in,\ni.e. when there are multiple merge bases.  (I can't think of a better\nlabel in such cases.)  But it is actually somewhat misleading when there\nis a unique merge base or no merge base.  Change this based on the\nnumber of merge bases:\n    >=2: \"merged common ancestors\"\n    1:   <abbreviated commit hash>\n    0:   \"<empty tree>\"\n\nAlso, the code in merge_3way making use of opt->ancestor was overly\ncomplex because it tried to handle the case of opt->ancestor being NULL.\nWe always set it first, though, so just add an assert that opt->ancestor\nis not NULL and simplify the surrounding code.\n\nTests have also been added to check that we get the right ancestor name\nfor each of the three cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |  39 ++++--\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 189 ++++++++++++++++++++++++++++++\n 3 files changed, 222 insertions(+), 14 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f1e2658457..6d5dce17fc 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1034,7 +1034,7 @@ static int merge_3way(struct merge_options *opt,\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n-\tchar *base_name, *name1, *name2;\n+\tchar *base, *name1, *name2;\n \tint merge_status;\n \n \tll_opts.renormalize = opt->renormalize;\n@@ -1058,16 +1058,13 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path && o->path);\n-\tif (strcmp(a->path, b->path) ||\n-\t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s:%s\", opt->ancestor, o->path);\n+\tassert(a->path && b->path && o->path && opt->ancestor);\n+\tif (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {\n+\t\tbase  = mkpathdup(\"%s:%s\", opt->ancestor, o->path);\n \t\tname1 = mkpathdup(\"%s:%s\", branch1, a->path);\n \t\tname2 = mkpathdup(\"%s:%s\", branch2, b->path);\n \t} else {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s\", opt->ancestor);\n+\t\tbase  = mkpathdup(\"%s\", opt->ancestor);\n \t\tname1 = mkpathdup(\"%s\", branch1);\n \t\tname2 = mkpathdup(\"%s\", branch2);\n \t}\n@@ -1076,11 +1073,11 @@ static int merge_3way(struct merge_options *opt,\n \tread_mmblob(&src1, &a->oid);\n \tread_mmblob(&src2, &b->oid);\n \n-\tmerge_status = ll_merge(result_buf, a->path, &orig, base_name,\n+\tmerge_status = ll_merge(result_buf, a->path, &orig, base,\n \t\t\t\t&src1, name1, &src2, name2,\n \t\t\t\topt->repo->index, &ll_opts);\n \n-\tfree(base_name);\n+\tfree(base);\n \tfree(name1);\n \tfree(name2);\n \tfree(orig.ptr);\n@@ -3523,6 +3520,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tstruct commit *merged_merge_bases;\n \tstruct tree *result_tree;\n \tint clean;\n+\tint num_merge_bases;\n+\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n@@ -3544,6 +3543,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n+\tnum_merge_bases = commit_list_count(merge_bases);\n \tmerged_merge_bases = pop_commit(&merge_bases);\n \tif (merged_merge_bases == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n@@ -3585,13 +3585,26 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tif (!opt->priv->call_depth)\n \t\trepo_read_index(opt->repo);\n \n-\topt->ancestor = \"merged common ancestors\";\n+\tswitch (num_merge_bases) {\n+\tcase 0:\n+\t\topt->ancestor = \"<empty tree>\";\n+\t\tbreak;\n+\tcase 1:\n+\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n+\t\t\t\t\t &merged_merge_bases->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\topt->ancestor = merge_base_abbrev.buf;\n+\t\tbreak;\n+\tdefault:\n+\t\topt->ancestor = \"merged common ancestors\";\n+\t}\n \tclean = merge_trees_internal(opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n \t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &result_tree);\n+\tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\n@@ -3644,6 +3657,8 @@ int merge_trees(struct merge_options *opt,\n \tint clean;\n \tstruct tree *ignored;\n \n+\tassert(opt->ancestor != NULL);\n+\n \tif (merge_start(opt, head))\n \t\treturn -1;\n \tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n@@ -3660,6 +3675,8 @@ int merge_recursive(struct merge_options *opt,\n {\n \tint clean;\n \n+\tassert(opt->ancestor == NULL);\n+\n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n \tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d23b948f27..7fddcc8c73 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1562,6 +1562,7 @@ test_expect_success 'check nested conflicts' '\n \t\tcd nested_conflicts &&\n \n \t\tgit clean -f &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L2^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1582,7 +1583,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:a >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1594,7 +1595,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:b >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1732,6 +1733,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t(\n \t\tcd virtual_merge_base_has_nested_conflicts &&\n \n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L3^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1760,7 +1762,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t\tcp left merged-once &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tmerged-once \\\n \t\t\tbase        \\\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nnew file mode 100755\nindex 0000000000..f69c8256bc\n--- /dev/null\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -0,0 +1,189 @@\n+#!/bin/sh\n+\n+test_description='recursive merge diff3 style conflict markers'\n+\n+. ./test-lib.sh\n+\n+# Setup:\n+#          L1\n+#            \\\n+#             ?\n+#            /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 both have a file named 'content' but have no common history\n+#\n+\n+test_expect_success 'setup no merge base' '\n+\ttest_create_repo no_merge_base &&\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout -b L &&\n+\t\ttest_commit A content A &&\n+\n+\t\tgit checkout --orphan R &&\n+\t\ttest_commit B content B\n+\t)\n+'\n+\n+test_expect_success 'check no merge base' '\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| <empty tree>\" content\n+\t)\n+'\n+\n+# Setup:\n+#          L1\n+#         /  \\\n+#   master    ?\n+#         \\  /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 have modified the same file ('content') in conflicting ways\n+#\n+\n+test_expect_success 'setup unique merge base' '\n+\ttest_create_repo unique_merge_base &&\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\ttest_commit base content \"1\n+2\n+3\n+4\n+5\n+\" &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\tgit checkout L &&\n+\t\ttest_commit L content \"1\n+2\n+3\n+4\n+5\n+7\" &&\n+\n+\t\tgit checkout R &&\n+\t\tgit rm content &&\n+\t\ttest_commit R renamed \"1\n+2\n+3\n+4\n+5\n+six\"\n+\t)\n+'\n+\n+test_expect_success 'check unique merge base' '\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| $MASTER:content\" renamed\n+\t)\n+'\n+\n+# Setup:\n+#          L1---L2--L3\n+#         /  \\ /      \\\n+#   master    X1       ?\n+#         \\  / \\      /\n+#          R1---R2--R3\n+#\n+# Where:\n+#   commits L1 and R1 have modified the same file in non-conflicting ways\n+#   X1 is an auto-generated merge-base used when merging L1 and R1\n+#   commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively\n+#   commits L3 and R3 both modify 'content' in conflicting ways\n+#\n+\n+test_expect_success 'setup multiple merge bases' '\n+\ttest_create_repo multiple_merge_bases &&\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\ttest_commit initial content \"1\n+2\n+3\n+4\n+5\" &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\ttest_commit L1 content \"0\n+1\n+2\n+3\n+4\n+5\" &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\ttest_commit R1 content \"1\n+2\n+3\n+4\n+5\n+6\" &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\tgit merge R1 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\tgit merge L1 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_commit L3 content \"0\n+1\n+2\n+3\n+4\n+5\n+A\" &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\tgit rm content &&\n+\t\ttest_commit R3 renamed \"0\n+2\n+3\n+4\n+5\n+six\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple merge bases' '\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| merged common ancestors:content\" renamed\n+\t)\n+'\n+\n+test_done\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379376","messageId":"20190726155258.28561-20-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v2 19/20] merge-recursive: be consistent with assert","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T15:52:57Z","receivedAt":"2019-07-26T15:53:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 8daec1df03de (\"merge-recursive: switch from (oid,mode) pairs\nto a diff_filespec\", 2019-04-05), an assertion on a->path && b->path\nwas added for code readability to document that these both needed to be\nnon-NULL at this point in the code.  However, the subsequent lines also\nread o->path, so it should be included in the assert.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 49eb50c17b..f1e2658457 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1058,7 +1058,7 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path);\n+\tassert(a->path && b->path && o->path);\n \tif (strcmp(a->path, b->path) ||\n \t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n \t\tbase_name = opt->ancestor == NULL ? NULL :\n-- \n2.22.0.550.g71c37a0928.dirty\n\n"},{"id":"379381","messageId":"xmqqo91godev.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-2-newren@gmail.com","subject":"Re: [PATCH v2 01/20] merge-recursive: fix minor memory leak in error condition","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T18:31:52Z","receivedAt":"2019-07-26T18:31:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Returning before freeing the allocated buffer is suboptimal; as with\n> elsewhere in the same function, make sure buf gets free'd.\n\nI do not have a real objection to the patch text, but the above\njustification does not make much sense to me.  The original code\nreturned an error when buf is NULL, so there is no leak returning\ndirectly, without jumping to free_buf: label (whose only effect is\nto free(buf) and return the value in ret).\n\nThe real value of this change is it may future-proof the codepath by\nmaking sure that everybody after an error goes to the same place to\nfree all resources---which happens to be only buf in the current\ncode, but this change allows it to change in the future, where some\nadditional resources may have been allocated before the call to\nread_object_file() and freed after free_buf: label.\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 8 +++++---\n>  1 file changed, 5 insertions(+), 3 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 12300131fc..1163508811 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -934,9 +934,11 @@ static int update_file_flags(struct merge_options *opt,\n>  \t\t}\n>  \n>  \t\tbuf = read_object_file(&contents->oid, &type, &size);\n> -\t\tif (!buf)\n> -\t\t\treturn err(opt, _(\"cannot read object %s '%s'\"),\n> -\t\t\t\t   oid_to_hex(&contents->oid), path);\n> +\t\tif (!buf) {\n> +\t\t\tret = err(opt, _(\"cannot read object %s '%s'\"),\n> +\t\t\t\t  oid_to_hex(&contents->oid), path);\n> +\t\t\tgoto free_buf;\n> +\t\t}\n>  \t\tif (type != OBJ_BLOB) {\n>  \t\t\tret = err(opt, _(\"blob expected for %s '%s'\"),\n>  \t\t\t\t  oid_to_hex(&contents->oid), path);\n"},{"id":"379382","messageId":"xmqqk1c4ocwd.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-3-newren@gmail.com","subject":"Re: [PATCH v2 02/20] merge-recursive: remove another implicit dependency on the_repository","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T18:42:58Z","receivedAt":"2019-07-26T18:43:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Commit d7cf3a96e9a0 (\"merge-recursive.c: remove implicit dependency on\n> the_repository\", 2019-01-12) and follow-ups like commit 34e7771bc644\n> (\"Use the right 'struct repository' instead of the_repository\",\n> 2019-06-27), removed most implicit uses of the_repository.  Convert\n> calls to get_commit_tree() to instead use repo_get_commit_tree() to get\n> rid of another.\n\nYou are getting rid of three more!!!\n\nNice ;-)\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 7 +++++--\n>  1 file changed, 5 insertions(+), 2 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 1163508811..37bb94fb4d 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3571,8 +3571,11 @@ int merge_recursive(struct merge_options *opt,\n>  \t\trepo_read_index(opt->repo);\n>  \n>  \topt->ancestor = \"merged common ancestors\";\n> -\tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n> -\t\t\t    get_commit_tree(merged_common_ancestors),\n> +\tclean = merge_trees(opt,\n> +\t\t\t    repo_get_commit_tree(opt->repo, h1),\n> +\t\t\t    repo_get_commit_tree(opt->repo, h2),\n> +\t\t\t    repo_get_commit_tree(opt->repo,\n> +\t\t\t\t\t\t merged_common_ancestors),\n>  \t\t\t    &mrtree);\n>  \tif (clean < 0) {\n>  \t\tflush_output(opt);\n"},{"id":"379384","messageId":"xmqqftmsobf5.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-4-newren@gmail.com","subject":"Re: [PATCH v2 03/20] Ensure index matches head before invoking merge machinery, round N","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T19:14:54Z","receivedAt":"2019-07-26T19:15:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> ...\n> So, load the index in builtin/merge-recursive.c, reload the in-memory\n> index in builtin/stash.c, and modify the t3030 testcase to correctly\n> setup the index and make sure that the test fails in the expected way\n> (meaning it reports a rename/rename conflict).\n\nThe last paragraph is what describes what this step does.  What is\nleft unsaid is how that small step relates to the change in the\nlarger picture (including the title).  We do want to have a code\nthat ensures the index has no higher-stage entries and match HEAD\nat the beginning of merge_recursive backend (as your lengthy preamble\nleading to the paragraph explains), but adding the code to populate\nin-core index to two codepaths does not make them check the condition\nautomatically---it is only half the story, no?\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  builtin/merge-recursive.c  | 4 ++++\n>  builtin/stash.c            | 2 ++\n>  t/t3030-merge-recursive.sh | 9 ++++++++-\n>  3 files changed, 14 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\n> index 5b910e351e..a4bfd8fc51 100644\n> --- a/builtin/merge-recursive.c\n> +++ b/builtin/merge-recursive.c\n> @@ -1,3 +1,4 @@\n> +#include \"cache.h\"\n>  #include \"builtin.h\"\n>  #include \"commit.h\"\n>  #include \"tag.h\"\n> @@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n>  \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n>  \t\tdie(_(\"not handling anything other than two heads merge.\"));\n>  \n> +\tif (repo_read_index_unmerged(the_repository))\n> +\t\tdie_resolve_conflict(\"merge\");\n> +\n>  \to.branch1 = argv[++i];\n>  \to.branch2 = argv[++i];\n>  \n> diff --git a/builtin/stash.c b/builtin/stash.c\n> index b5a301f24d..4aa47785f9 100644\n> --- a/builtin/stash.c\n> +++ b/builtin/stash.c\n> @@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n>  \t\t\t\treturn error(_(\"could not save index tree\"));\n>  \n>  \t\t\treset_head();\n> +\t\t\tdiscard_cache();\n> +\t\t\tread_cache();\n>  \t\t}\n>  \t}\n>  \n> diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\n> index ff641b348a..a37bcc58a0 100755\n> --- a/t/t3030-merge-recursive.sh\n> +++ b/t/t3030-merge-recursive.sh\n> @@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '\n>  test_expect_success 'merge-recursive remembers the names of all base trees' '\n>  \tgit reset --hard HEAD &&\n>  \n> +\t# make the index match $c1 so that merge-recursive below does not\n> +\t# fail early\n> +\tgit diff --binary HEAD $c1 -- | git apply --cached &&\n> +\n>  \t# more trees than static slots used by oid_to_hex()\n>  \tfor commit in $c0 $c2 $c4 $c5 $c6 $c7\n>  \tdo\n>  \t\tgit rev-parse \"$commit^{tree}\"\n>  \tdone >trees &&\n>  \n> -\t# ignore the return code -- it only fails because the input is weird\n> +\t# ignore the return code; it only fails because the input is weird...\n>  \ttest_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&\n>  \n> +\t# ...but make sure it fails in the expected way\n> +\ttest_i18ngrep CONFLICT.*rename/rename out &&\n> +\n>  \t# merge-recursive prints in reverse order, but we do not care\n>  \tsort <trees >expect &&\n>  \tsed -n \"s/^virtual //p\" out | sort >actual &&\n"},{"id":"379385","messageId":"xmqqblxgoamb.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-5-newren@gmail.com","subject":"Re: [PATCH v2 04/20] merge-recursive: exit early if index != head","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T19:32:12Z","receivedAt":"2019-07-26T19:32:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Make sure we do the index == head check at the beginning of the merge,\n> and error out immediately if it fails.  While we're at it, fix a small\n> leak in the show-the-error codepath.\n\nAs the call to repo_index_has_changes() is moved to the very\nbeginning of merge_recursive() and merge_trees(), the workhorse of\nthe merge machinery, merge_trees_internal(), can lose it.\n\n> +static int merge_start(struct merge_options *opt, struct tree *head)\n> +{\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\n> +\tassert(opt->branch1 && opt->branch2);\n\nThis is a new assertion that did not exist in the original, isn't\nit?  I do not object to new sensible assertions, and I think these\ntwo fields must be non-null in a freshly initialized merge_options\nstructure, but shouldn't we be discussing if these two fields should\nbe non-NULL, and if there are other fields in the same structure\nthat we should be adding new assertions on, in a separate step on\nits own?\n\n> +\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n> +\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n> +\t\t    sb.buf);\n> +\t\tstrbuf_release(&sb);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n"},{"id":"379386","messageId":"xmqq7e84oady.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-6-newren@gmail.com","subject":"Re: [PATCH v2 05/20] merge-recursive: remove useless parameter in merge_trees()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T19:37:13Z","receivedAt":"2019-07-26T19:37:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> merge_trees() took a results parameter that would only be written when\n> opt->call_depth was positive, which is never the case now that\n> merge_trees_internal() has been split from merge_trees().  Remove the\n> misleading and unused parameter from merge_trees().\n>\n> While at it, add some comments explaining how the output of\n> merge_trees() and merge_recursive() differ.\n\nMakes sense.\n"},{"id":"379389","messageId":"xmqq36iso9gb.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-7-newren@gmail.com","subject":"Re: [PATCH v2 06/20] merge-recursive: don't force external callers to do our logging","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T19:57:24Z","receivedAt":"2019-07-26T19:57:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Alternatively, you can view this as \"make the merge functions behave\n> more similarly.\"  merge-recursive has three different entry points:\n> merge_trees(), merge_recursive(), and merge_recursive_generic().  Two of\n> these would call diff_warn_rename_limit(), but merge_trees() didn't.\n> This lead to callers of merge_trees() needing to manually call\n> diff_warn_rename_limit() themselves.  Move this to the new\n> merge_finalize() function to make sure that all three entry points run\n> this function.\n\nInteresting.  It seems that b520abf1c8f did a suboptimal jobs but\nthis step cleans it up quite nicely.\n\nAre there callers of merge_trees() in other codepaths, and do they\nwant this change?\n\nThere is only one, builtin/checkout.c::merge_working_tree(), which\nis used when switching to a different commit.  I think it would not\nhurt to give the same warning (but I also think it would not hurt to\nsimply disable rename detection in that context); we should record\nthe whatever decision we make in the log message.\n\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 7 +++----\n>  sequencer.c       | 1 -\n>  2 files changed, 3 insertions(+), 5 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 4a481c3929..308e350ff1 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3583,9 +3583,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \tflush_output(opt);\n>  \tif (!opt->call_depth && opt->buffer_output < 2)\n>  \t\tstrbuf_release(&opt->obuf);\n> -\tif (show(opt, 2))\n> -\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n> -\t\t\t\t       opt->needed_rename_limit, 0);\n>  \treturn clean;\n>  }\n>  \n> @@ -3607,7 +3604,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n>  \n>  static void merge_finalize(struct merge_options *opt)\n>  {\n> -\t/* Common code for wrapping up merges will be added here later */\n> +\tif (show(opt, 2))\n> +\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n> +\t\t\t\t       opt->needed_rename_limit, 0);\n>  }\n>  \n>  int merge_trees(struct merge_options *opt,\n> diff --git a/sequencer.c b/sequencer.c\n> index c4ed30f1b4..094a4dd03d 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -617,7 +617,6 @@ static int do_recursive_merge(struct repository *r,\n>  \tif (is_rebase_i(opts) && clean <= 0)\n>  \t\tfputs(o.obuf.buf, stdout);\n>  \tstrbuf_release(&o.obuf);\n> -\tdiff_warn_rename_limit(\"merge.renamelimit\", o.needed_rename_limit, 0);\n>  \tif (clean < 0) {\n>  \t\trollback_lock_file(&index_lock);\n>  \t\treturn clean;\n"},{"id":"379391","messageId":"xmqqy30kmu8r.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190726155258.28561-8-newren@gmail.com","subject":"Re: [PATCH v2 07/20] Use write_index_as_tree() in lieu of write_tree_from_memory()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-26T20:11:16Z","receivedAt":"2019-07-26T20:11:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> diff --git a/cache-tree.c b/cache-tree.c\n> index 706ffcf188..99144b1704 100644\n> --- a/cache-tree.c\n> +++ b/cache-tree.c\n> @@ -613,14 +613,19 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n>  \tint entries, was_valid;\n>  \tstruct lock_file lock_file = LOCK_INIT;\n>  \tint ret = 0;\n> +\tint access_disk = !(flags & WRITE_TREE_FROM_MEMORY);\n\nShouldn't we go one step futher and make the bulk of in-core index\nprocessing into a new helper function, while making\nwrite_index_as_tree() a thin-wrapper around it, i.e.\n\n\twrite_index_as_tree() \n\t{\n\t\tlock the index for update;\n\t\tread the on-disk index;\n\t\tcall that new helper function to write a tree;\n\t\tupdate the on-disk index;\n\t}\n\nand reuse the helper from\nmerge-recursive.c::write_tree_from_memory() while keeping the call\nto the latter in merge_trees_internal()?  Wouldn't that approach\nlet you do this without adding an extra flag bit?\n\nAlso, there used to be a check to ensure that the in-core index fed\nto write_tree_from_memory() is fully merged and otherwise dump the\nunmerged entries with BUG().  Can we simply lose it?  I know you\nreturn with \"error building trees\" from merge_trees_internal() but\nit does not BUG().\n"},{"id":"379412","messageId":"CABPp-BHWhhjovb0ENcUva1Y2hJ2GzypefhQhZba5usQUnaBWsQ@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqo91godev.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 01/20] merge-recursive: fix minor memory leak in error condition","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T23:19:58Z","receivedAt":"2019-07-26T23:20:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 26, 2019 at 11:31 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Returning before freeing the allocated buffer is suboptimal; as with\n> > elsewhere in the same function, make sure buf gets free'd.\n>\n> I do not have a real objection to the patch text, but the above\n> justification does not make much sense to me.  The original code\n> returned an error when buf is NULL, so there is no leak returning\n> directly, without jumping to free_buf: label (whose only effect is\n> to free(buf) and return the value in ret).\n>\n> The real value of this change is it may future-proof the codepath by\n> making sure that everybody after an error goes to the same place to\n> free all resources---which happens to be only buf in the current\n> code, but this change allows it to change in the future, where some\n> additional resources may have been allocated before the call to\n> read_object_file() and freed after free_buf: label.\n\nIndeed, not sure how I overlooked that buf was NULL since that was the\nprecise check I was modifying.  I'll clean up the commit message.\n"},{"id":"379413","messageId":"CABPp-BFZvVDjg0tZM9EHh9cse2j0QXRdbVKmK0emq696xO3sEg@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqftmsobf5.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 03/20] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T23:22:34Z","receivedAt":"2019-07-26T23:22:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 26, 2019 at 12:15 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > ...\n> > So, load the index in builtin/merge-recursive.c, reload the in-memory\n> > index in builtin/stash.c, and modify the t3030 testcase to correctly\n> > setup the index and make sure that the test fails in the expected way\n> > (meaning it reports a rename/rename conflict).\n>\n> The last paragraph is what describes what this step does.  What is\n> left unsaid is how that small step relates to the change in the\n> larger picture (including the title).  We do want to have a code\n> that ensures the index has no higher-stage entries and match HEAD\n> at the beginning of merge_recursive backend (as your lengthy preamble\n> leading to the paragraph explains), but adding the code to populate\n> in-core index to two codepaths does not make them check the condition\n> automatically---it is only half the story, no?\n\nIndeed; the other half is the movement of the index==head check in\npatch 4 so that it happens immediately and ensures we don't run into\nthis problem again.  I can add a note to the commit message that the\nnext patch in the series will be taking care of that half of the\nproblem.\n"},{"id":"379414","messageId":"CABPp-BHKWV6pNk_72xVTr5-rJ6n71Q9=SFa2H75_FUhJ8DrUQQ@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqblxgoamb.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 04/20] merge-recursive: exit early if index != head","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T23:26:27Z","receivedAt":"2019-07-26T23:26:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 26, 2019 at 12:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Make sure we do the index == head check at the beginning of the merge,\n> > and error out immediately if it fails.  While we're at it, fix a small\n> > leak in the show-the-error codepath.\n>\n> As the call to repo_index_has_changes() is moved to the very\n> beginning of merge_recursive() and merge_trees(), the workhorse of\n> the merge machinery, merge_trees_internal(), can lose it.\n\nIs this just a re-summarization (a perfectly good one), or a\nsuggestion for alternate wording for the commit message?\n\n> > +static int merge_start(struct merge_options *opt, struct tree *head)\n> > +{\n> > +     struct strbuf sb = STRBUF_INIT;\n> > +\n> > +     assert(opt->branch1 && opt->branch2);\n>\n> This is a new assertion that did not exist in the original, isn't\n> it?  I do not object to new sensible assertions, and I think these\n> two fields must be non-null in a freshly initialized merge_options\n> structure, but shouldn't we be discussing if these two fields should\n> be non-NULL, and if there are other fields in the same structure\n> that we should be adding new assertions on, in a separate step on\n> its own?\n\nGood point.  The only other one I saw was opt->ancestor, and while it\ndoes have conditions it should satisfy, somewhat surprisingly the\ncondition is the opposite in merge_trees() vs. merge_recursive().  So\nI think this is the only check that makes sense to add to\nmerge_start(), but I can move that out into a separate patch and add\nsome words about why just these two.\n"},{"id":"379415","messageId":"CABPp-BG35XHN9H9VBYw4JMHknftMhB6u3H=CDFHhM7EMinQNSQ@mail.gmail.com","threadId":"51526","inReplyTo":"xmqq36iso9gb.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 06/20] merge-recursive: don't force external callers to do our logging","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T23:28:49Z","receivedAt":"2019-07-26T23:29:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 26, 2019 at 12:57 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Alternatively, you can view this as \"make the merge functions behave\n> > more similarly.\"  merge-recursive has three different entry points:\n> > merge_trees(), merge_recursive(), and merge_recursive_generic().  Two of\n> > these would call diff_warn_rename_limit(), but merge_trees() didn't.\n> > This lead to callers of merge_trees() needing to manually call\n> > diff_warn_rename_limit() themselves.  Move this to the new\n> > merge_finalize() function to make sure that all three entry points run\n> > this function.\n>\n> Interesting.  It seems that b520abf1c8f did a suboptimal jobs but\n> this step cleans it up quite nicely.\n>\n> Are there callers of merge_trees() in other codepaths, and do they\n> want this change?\n>\n> There is only one, builtin/checkout.c::merge_working_tree(), which\n> is used when switching to a different commit.  I think it would not\n> hurt to give the same warning (but I also think it would not hurt to\n> simply disable rename detection in that context); we should record\n> the whatever decision we make in the log message.\n\nI was surprised to read this because I thought I did that...but I\nguess that was in patch 15 for a slightly different case.  Yeah, I can\nadd some words to the commit message about this.\n"},{"id":"379416","messageId":"CABPp-BExLuH9mz6sgjTbKXg44zmT3PcRsy+A8s-9PAQuxDuXfw@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqy30kmu8r.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 07/20] Use write_index_as_tree() in lieu of write_tree_from_memory()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-07-26T23:39:05Z","receivedAt":"2019-07-26T23:39:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jul 26, 2019 at 1:11 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > diff --git a/cache-tree.c b/cache-tree.c\n> > index 706ffcf188..99144b1704 100644\n> > --- a/cache-tree.c\n> > +++ b/cache-tree.c\n> > @@ -613,14 +613,19 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n> >       int entries, was_valid;\n> >       struct lock_file lock_file = LOCK_INIT;\n> >       int ret = 0;\n> > +     int access_disk = !(flags & WRITE_TREE_FROM_MEMORY);\n>\n> Shouldn't we go one step futher and make the bulk of in-core index\n> processing into a new helper function, while making\n> write_index_as_tree() a thin-wrapper around it, i.e.\n>\n>         write_index_as_tree()\n>         {\n>                 lock the index for update;\n>                 read the on-disk index;\n>                 call that new helper function to write a tree;\n>                 update the on-disk index;\n>         }\n>\n> and reuse the helper from\n> merge-recursive.c::write_tree_from_memory() while keeping the call\n> to the latter in merge_trees_internal()?  Wouldn't that approach\n> let you do this without adding an extra flag bit?\n\nI thought about that briefly yesterday, but the fact that the\nwrite_locked_index() call only happens if !cache_tree_fully_valid()\nmeant refactoring slightly more to get the helper to also return that\nboolean value, and since I was a little unsure of myself with\ncache-tree stuff in general I wanted to propose what looked like the\nminimally invasive changes first (by which I mean smallest patch).\nI'll take a closer look at this path.\n\n> Also, there used to be a check to ensure that the in-core index fed\n> to write_tree_from_memory() is fully merged and otherwise dump the\n> unmerged entries with BUG().  Can we simply lose it?  I know you\n> return with \"error building trees\" from merge_trees_internal() but\n> it does not BUG().\n\nI thought about that yesterday and decided, \"Nah, it's a developer\nonly debug message used during development.  I've _never_ seen anyone\nreport those messages, and I only saw them when I was making bad\nchanges during development when I first started a decade ago.\"  Then\nEmily posts a report today showing that exact BUG message being hit\nwith git-2.22.0, and how she wouldn't have been able to get all that\nextra information in her analysis without that bit of information\nbeing reported.\n\nSo, yeah, I need to put something from those BUG() messages back in;\nthey clearly helped with that issue, and might help again in the\nfuture.\n"},{"id":"379467","messageId":"xmqqr269la6q.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"CABPp-BExLuH9mz6sgjTbKXg44zmT3PcRsy+A8s-9PAQuxDuXfw@mail.gmail.com","subject":"Re: [PATCH v2 07/20] Use write_index_as_tree() in lieu of write_tree_from_memory()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-29T04:46:37Z","receivedAt":"2019-07-29T04:46:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> I thought about that briefly yesterday, but the fact that the\n> write_locked_index() call only happens if !cache_tree_fully_valid()\n> meant refactoring slightly more to get the helper to also return that\n> boolean value, and since I was a little unsure of myself with\n> cache-tree stuff in general I wanted to propose what looked like the\n> minimally invasive changes first (by which I mean smallest patch).\n\nOr have the caller check if cache-tree is fully valid, which is the\nonly case that you can build a tree (and a fully merged index would\nbe fully valid after you do cache_tree_update()).\n\n> I'll take a closer look at this path.\n> ...\n> So, yeah, I need to put something from those BUG() messages back in;\n> they clearly helped with that issue, and might help again in the\n> future.\n\nThanks.\n"},{"id":"379499","messageId":"698e7154-b625-dfe5-ccf7-87e34c77e480@gmail.com","threadId":"51526","inReplyTo":"xmqqo91godev.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 01/20] merge-recursive: fix minor memory leak in error condition","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2019-07-29T14:13:19Z","receivedAt":"2019-07-29T14:13:23Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 7/26/2019 2:31 PM, Junio C Hamano wrote:\n> Elijah Newren <newren@gmail.com> writes:\n> \n>> Returning before freeing the allocated buffer is suboptimal; as with\n>> elsewhere in the same function, make sure buf gets free'd.\n> \n> I do not have a real objection to the patch text, but the above\n> justification does not make much sense to me.  The original code\n> returned an error when buf is NULL, so there is no leak returning\n> directly, without jumping to free_buf: label (whose only effect is\n> to free(buf) and return the value in ret).\n> \n> The real value of this change is it may future-proof the codepath by\n> making sure that everybody after an error goes to the same place to\n> free all resources---which happens to be only buf in the current\n> code, but this change allows it to change in the future, where some\n> additional resources may have been allocated before the call to\n> read_object_file() and freed after free_buf: label.\n\nIt should be noted that any future change to make the \"free_buf:\" label\nmean \"free everything\" then it should be renamed to \"cleanup:\" or similar.\n\nNot that we should do that now, as that would muddy the blame.\n\nThanks,\n-Stolee\n"},{"id":"379507","messageId":"xmqqy30gvnqd.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"CABPp-BHKWV6pNk_72xVTr5-rJ6n71Q9=SFa2H75_FUhJ8DrUQQ@mail.gmail.com","subject":"Re: [PATCH v2 04/20] merge-recursive: exit early if index != head","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-29T15:56:10Z","receivedAt":"2019-07-29T15:56:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> On Fri, Jul 26, 2019 at 12:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Elijah Newren <newren@gmail.com> writes:\n>>\n>> > Make sure we do the index == head check at the beginning of the merge,\n>> > and error out immediately if it fails.  While we're at it, fix a small\n>> > leak in the show-the-error codepath.\n>>\n>> As the call to repo_index_has_changes() is moved to the very\n>> beginning of merge_recursive() and merge_trees(), the workhorse of\n>> the merge machinery, merge_trees_internal(), can lose it.\n>\n> Is this just a re-summarization (a perfectly good one), or a\n> suggestion for alternate wording for the commit message?\n\nThe usual \"thinking aloud to see if I understood the patch and the\nauthor's thought behind it, and give others a chance to correct me\"\nkind of review.\n"},{"id":"380494","messageId":"20190815214053.16594-1-newren@gmail.com","threadId":"51526","inReplyTo":"20190726155258.28561-1-newren@gmail.com","subject":"[PATCH v3 00/24] Clean up merge API","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:29Z","receivedAt":"2019-08-15T21:41:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Before writing a replacement merge strategy for recursive, I decided\nto first cleanup the merge API -- streamlining merge-recursive.h and\nmaking it more readable.\n\nThanks to Dscho and Junio for feedback on v1 & v2.\n\nChanges since v2:\n  * Addressed feedback from Junio and Dscho\n    * splitting a couple patches they highlighted as needing splitting\n    * fixed up several commit messages\n    * refactored cache-tree write-index-as-tree functions to share code\n    * created a separate commit with more sanity checks for merge_options\n  * Rebased on master (due to en/disable-dir-rename-in-recursive-merge)\n  * Found and fixed a minor bug in checkout; put this and related patches\n    at the beginning of the series\n  * Incorporated a patch from Stolee, though I removed the repo-settings\n    bits to make our patch series independent, and modified it to update\n    more existing code to use the new enum values.\n\nThings I'd like reviewers to focus on, in priority order:\n  * Patch 12 -- I'm still unsure about cache-tree stuff\n  * The new patches (2, 3, 5, 23); in more detail:\n    * Patch 5 from Stolee -- I modified his original patch a fair amount\n      but left him as author; it's a simple patch, but extra eyes to\n      verify I didn't blame him for some nasty mistake of mine would be\n      good.\n    * Patch 2 fixes a minor issue with checkout -m\n    * Patch 23 is the more thorough sanity checks Junio suggested\n    * Patch 3 was mostly split out from what is now patch 4, so it was\n      mostly reviewed before but it now ties in nicely with patch 2.\n\nDerrick Stolee (1):\n  merge-recursive: introduce an enum for detect_directory_renames values\n\nElijah Newren (23):\n  merge-recursive: be consistent with assert\n  checkout: provide better conflict hunk description with detached HEAD\n  merge-recursive: enforce opt->ancestor != NULL when calling\n    merge_trees()\n  merge-recursive: provide a better label for diff3 common ancestor\n  merge-recursive: future-proof update_file_flags() against memory leaks\n  merge-recursive: remove another implicit dependency on the_repository\n  Ensure index matches head before invoking merge machinery, round N\n  merge-recursive: exit early if index != head\n  merge-recursive: remove useless parameter in merge_trees()\n  merge-recursive: don't force external callers to do our logging\n  cache-tree: share code between functions writing an index as a tree\n  merge-recursive: fix some overly long lines\n  merge-recursive: use common name for ancestors/common/base_list\n  merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n  merge-recursive: rename merge_options argument to opt in header\n  merge-recursive: move some definitions around to clean up the header\n  merge-recursive: consolidate unnecessary fields in merge_options\n  merge-recursive: comment and reorder the merge_options fields\n  merge-recursive: avoid losing output and leaking memory holding that\n    output\n  merge-recursive: split internal fields into a separate struct\n  merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n  merge-recursive: add sanity checks for relevant merge_options\n  merge-recursive: alphabetize include list\n\n builtin/am.c                      |   2 +-\n builtin/checkout.c                |  14 +-\n builtin/merge-recursive.c         |   4 +\n builtin/stash.c                   |   2 +\n cache-tree.c                      |  81 +++--\n cache-tree.h                      |   3 +-\n merge-recursive.c                 | 571 ++++++++++++++++++------------\n merge-recursive.h                 | 164 +++++----\n sequencer.c                       |   5 +-\n t/t3030-merge-recursive.sh        |   9 +-\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 189 ++++++++++\n 12 files changed, 718 insertions(+), 334 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\nRange-diff:\n19:  d6158e555d =  1:  21f1e04dc9 merge-recursive: be consistent with assert\n -:  ---------- >  2:  ac24702773 checkout: provide better conflict hunk description with detached HEAD\n -:  ---------- >  3:  fd14ed9490 merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()\n20:  814a65ecab !  4:  540a1d17d7 merge-recursive: provide a better label for diff3 common ancestor\n    @@ Commit message\n             1:   <abbreviated commit hash>\n             0:   \"<empty tree>\"\n     \n    -    Also, the code in merge_3way making use of opt->ancestor was overly\n    -    complex because it tried to handle the case of opt->ancestor being NULL.\n    -    We always set it first, though, so just add an assert that opt->ancestor\n    -    is not NULL and simplify the surrounding code.\n    -\n         Tests have also been added to check that we get the right ancestor name\n         for each of the three cases.\n     \n    +    Also, since merge_recursive() and merge_trees() have polar opposite\n    +    pre-conditions for opt->ancestor, document merge_recursive()'s\n    +    pre-condition with an assertion.  (An assertion was added to\n    +    merge_trees() already a few commits ago.)  The differences in\n    +    pre-conditions stem from two factors: (1) merge_trees() does not recurse\n    +    and thus does not have multiple sub-merges to worry about -- each of\n    +    which would require a different value for opt->ancestor, (2)\n    +    merge_trees() is only passed trees rather than commits and thus cannot\n    +    internally guess as good of a label.  Thus, while external callers of\n    +    merge_trees() are required to provide a non-NULL opt->ancestor,\n    +    merge_recursive() expects to set this value itself.\n    +\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n     \n      ## merge-recursive.c ##\n    -@@ merge-recursive.c: static int merge_3way(struct merge_options *opt,\n    - {\n    - \tmmfile_t orig, src1, src2;\n    - \tstruct ll_merge_options ll_opts = {0};\n    --\tchar *base_name, *name1, *name2;\n    -+\tchar *base, *name1, *name2;\n    - \tint merge_status;\n    - \n    - \tll_opts.renormalize = opt->renormalize;\n    -@@ merge-recursive.c: static int merge_3way(struct merge_options *opt,\n    - \t\t}\n    - \t}\n    - \n    --\tassert(a->path && b->path && o->path);\n    --\tif (strcmp(a->path, b->path) ||\n    --\t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n    --\t\tbase_name = opt->ancestor == NULL ? NULL :\n    --\t\t\tmkpathdup(\"%s:%s\", opt->ancestor, o->path);\n    -+\tassert(a->path && b->path && o->path && opt->ancestor);\n    -+\tif (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {\n    -+\t\tbase  = mkpathdup(\"%s:%s\", opt->ancestor, o->path);\n    - \t\tname1 = mkpathdup(\"%s:%s\", branch1, a->path);\n    - \t\tname2 = mkpathdup(\"%s:%s\", branch2, b->path);\n    - \t} else {\n    --\t\tbase_name = opt->ancestor == NULL ? NULL :\n    --\t\t\tmkpathdup(\"%s\", opt->ancestor);\n    -+\t\tbase  = mkpathdup(\"%s\", opt->ancestor);\n    - \t\tname1 = mkpathdup(\"%s\", branch1);\n    - \t\tname2 = mkpathdup(\"%s\", branch2);\n    - \t}\n    -@@ merge-recursive.c: static int merge_3way(struct merge_options *opt,\n    - \tread_mmblob(&src1, &a->oid);\n    - \tread_mmblob(&src2, &b->oid);\n    - \n    --\tmerge_status = ll_merge(result_buf, a->path, &orig, base_name,\n    -+\tmerge_status = ll_merge(result_buf, a->path, &orig, base,\n    - \t\t\t\t&src1, name1, &src2, name2,\n    - \t\t\t\topt->repo->index, &ll_opts);\n    - \n    --\tfree(base_name);\n    -+\tfree(base);\n    - \tfree(name1);\n    - \tfree(name2);\n    - \tfree(orig.ptr);\n    -@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n    - \tstruct commit *merged_merge_bases;\n    - \tstruct tree *result_tree;\n    +@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    + \tstruct commit *merged_common_ancestors;\n    + \tstruct tree *mrtree;\n      \tint clean;\n     +\tint num_merge_bases;\n     +\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n    ++\n    ++\tif (!opt->call_depth)\n    ++\t\tassert(opt->ancestor == NULL);\n      \n      \tif (show(opt, 4)) {\n      \t\toutput(opt, 4, _(\"Merging:\"));\n    -@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n    +@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \t\t\toutput_commit_title(opt, iter->item);\n      \t}\n      \n    -+\tnum_merge_bases = commit_list_count(merge_bases);\n    - \tmerged_merge_bases = pop_commit(&merge_bases);\n    - \tif (merged_merge_bases == NULL) {\n    ++\tnum_merge_bases = commit_list_count(ca);\n    + \tmerged_common_ancestors = pop_commit(&ca);\n    + \tif (merged_common_ancestors == NULL) {\n      \t\t/* if there is no common ancestor, use an empty tree */\n    -@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n    - \tif (!opt->priv->call_depth)\n    +@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    + \tif (!opt->call_depth)\n      \t\trepo_read_index(opt->repo);\n      \n     -\topt->ancestor = \"merged common ancestors\";\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     +\t\tbreak;\n     +\tcase 1:\n     +\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n    -+\t\t\t\t\t &merged_merge_bases->object.oid,\n    ++\t\t\t\t\t &merged_common_ancestors->object.oid,\n     +\t\t\t\t\t DEFAULT_ABBREV);\n     +\t\topt->ancestor = merge_base_abbrev.buf;\n     +\t\tbreak;\n     +\tdefault:\n     +\t\topt->ancestor = \"merged common ancestors\";\n     +\t}\n    - \tclean = merge_trees_internal(opt,\n    - \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n    - \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n    - \t\t\t\t     repo_get_commit_tree(opt->repo,\n    - \t\t\t\t\t\t\t  merged_merge_bases),\n    - \t\t\t\t     &result_tree);\n    + \tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n    + \t\t\t    get_commit_tree(merged_common_ancestors),\n    + \t\t\t    &mrtree);\n     +\tstrbuf_release(&merge_base_abbrev);\n      \tif (clean < 0) {\n      \t\tflush_output(opt);\n      \t\treturn clean;\n    -@@ merge-recursive.c: int merge_trees(struct merge_options *opt,\n    - \tint clean;\n    - \tstruct tree *ignored;\n    - \n    -+\tassert(opt->ancestor != NULL);\n    -+\n    - \tif (merge_start(opt, head))\n    - \t\treturn -1;\n    - \tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n    -@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    - {\n    - \tint clean;\n    - \n    -+\tassert(opt->ancestor == NULL);\n    -+\n    - \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n    - \t\treturn -1;\n    - \tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n     \n      ## t/t6036-recursive-corner-cases.sh ##\n     @@ t/t6036-recursive-corner-cases.sh: test_expect_success 'check nested conflicts' '\n -:  ---------- >  5:  19ff6a9503 merge-recursive: introduce an enum for detect_directory_renames values\n 1:  a640f0f2d0 !  6:  5e44146da1 merge-recursive: fix minor memory leak in error condition\n    @@ Metadata\n     Author: Elijah Newren <newren@gmail.com>\n     \n      ## Commit message ##\n    -    merge-recursive: fix minor memory leak in error condition\n    +    merge-recursive: future-proof update_file_flags() against memory leaks\n     \n    -    Returning before freeing the allocated buffer is suboptimal; as with\n    -    elsewhere in the same function, make sure buf gets free'd.\n    +    There is a 'free_buf' label to which all but one of the error paths in\n    +    update_file_flags() jump; that error case involves a NULL buf and is\n    +    thus not a memory leak.  However, make that error case execute the same\n    +    deallocation code anyway so that if anyone adds any additional memory\n    +    allocations or deallocations, then all error paths correctly deallocate\n    +    resources.\n     \n         Signed-off-by: Elijah Newren <newren@gmail.com>\n     \n 2:  34f0891d96 <  -:  ---------- merge-recursive: remove another implicit dependency on the_repository\n -:  ---------- >  7:  df210eb029 merge-recursive: remove another implicit dependency on the_repository\n 3:  26739a7ed0 !  8:  74dd7b8f59 Ensure index matches head before invoking merge machinery, round N\n    @@ Commit message\n         So, load the index in builtin/merge-recursive.c, reload the in-memory\n         index in builtin/stash.c, and modify the t3030 testcase to correctly\n         setup the index and make sure that the test fails in the expected way\n    -    (meaning it reports a rename/rename conflict).\n    +    (meaning it reports a rename/rename conflict).  This makes sure that\n    +    all callers actually make the index match head.  The next commit will\n    +    then enforce the condition that index matches head earlier so this\n    +    problem doesn't return in the future.\n     \n         Signed-off-by: Elijah Newren <newren@gmail.com>\n     \n 4:  76cb459b99 !  9:  f04eba4184 merge-recursive: exit early if index != head\n    @@ merge-recursive.c: static int process_entry(struct merge_options *opt,\n      \tint code, clean;\n     -\tstruct strbuf sb = STRBUF_INIT;\n     -\n    +-\tassert(opt->ancestor != NULL);\n    +-\n     -\tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n     -\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n     -\t\t    sb.buf);\n    @@ merge-recursive.c: static struct commit_list *reverse_commit_list(struct commit_\n      {\n      \tstruct commit_list *iter;\n      \tstruct commit *merged_common_ancestors;\n    +@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    + \tint num_merge_bases;\n    + \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n    + \n    +-\tif (!opt->call_depth)\n    +-\t\tassert(opt->ancestor == NULL);\n    +-\n    + \tif (show(opt, 4)) {\n    + \t\toutput(opt, 4, _(\"Merging:\"));\n    + \t\toutput_commit_title(opt, h1);\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \t\tsaved_b2 = opt->branch2;\n      \t\topt->branch1 = \"Temporary merge branch 1\";\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \t\t\treturn -1;\n      \t\topt->branch1 = saved_b1;\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    - \t\trepo_read_index(opt->repo);\n    - \n    - \topt->ancestor = \"merged common ancestors\";\n    + \tdefault:\n    + \t\topt->ancestor = \"merged common ancestors\";\n    + \t}\n     -\tclean = merge_trees(opt,\n     -\t\t\t    repo_get_commit_tree(opt->repo, h1),\n     -\t\t\t    repo_get_commit_tree(opt->repo, h2),\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n     +\t\t\t\t     repo_get_commit_tree(opt->repo,\n     +\t\t\t\t\t\t\t  merged_common_ancestors),\n     +\t\t\t\t     &mrtree);\n    + \tstrbuf_release(&merge_base_abbrev);\n      \tif (clean < 0) {\n      \t\tflush_output(opt);\n    - \t\treturn clean;\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \treturn clean;\n      }\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n     +{\n     +\tstruct strbuf sb = STRBUF_INIT;\n     +\n    -+\tassert(opt->branch1 && opt->branch2);\n    -+\n     +\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n     +\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n     +\t\t    sb.buf);\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n     +{\n     +\tint clean;\n     +\n    ++\tassert(opt->ancestor != NULL);\n    ++\n     +\tif (merge_start(opt, head))\n     +\t\treturn -1;\n     +\tclean = merge_trees_internal(opt, head, merge, common, result);\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n     +{\n     +\tint clean;\n     +\n    ++\tassert(opt->ancestor == NULL);\n    ++\n     +\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n     +\t\treturn -1;\n     +\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n 5:  2560458522 ! 10:  8688d40739 merge-recursive: remove useless parameter in merge_trees()\n    @@ merge-recursive.c: static void merge_finalize(struct merge_options *opt)\n      \tint clean;\n     +\tstruct tree *ignored;\n      \n    + \tassert(opt->ancestor != NULL);\n    + \n      \tif (merge_start(opt, head))\n      \t\treturn -1;\n     -\tclean = merge_trees_internal(opt, head, merge, common, result);\n 6:  fb340fbe56 ! 11:  a92d460707 merge-recursive: don't force external callers to do our logging\n    @@ Commit message\n         merge_finalize() function to make sure that all three entry points run\n         this function.\n     \n    +    Note that there are two external callers of merge_trees(), one in\n    +    sequencer.c and one in builtin/checkout.c.  The one in sequencer.c is\n    +    cleaned up by this patch and just transfers where the call to\n    +    diff_warn_rename_limit() is made; the one in builtin/checkout.c is for\n    +    switching to a different commit and in the very rare case where the\n    +    warning might be triggered, it would probably be helpful to include\n    +    (e.g. if someone is modifying a file that has been renamed in moving to\n    +    the other commit, but there are so many renames between the commits that\n    +    the limit kicks in and none are detected, it may help to have an\n    +    explanation about why they got a delete/modify conflict instead of a\n    +    proper content merge in a renamed file).\n    +\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n     \n      ## merge-recursive.c ##\n 7:  884305a3a6 <  -:  ---------- Use write_index_as_tree() in lieu of write_tree_from_memory()\n -:  ---------- > 12:  dec0ea7409 cache-tree: share code between functions writing an index as a tree\n 8:  870937b31f = 13:  b51f3d1924 merge-recursive: fix some overly long lines\n 9:  5127b75ac4 ! 14:  a069cc4cca merge-recursive: use common name for ancestors/common/base_list\n    @@ merge-recursive.c: static struct commit_list *reverse_commit_list(struct commit_\n     +\tstruct commit *merged_merge_bases;\n      \tstruct tree *mrtree;\n      \tint clean;\n    - \n    + \tint num_merge_bases;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\toutput_commit_title(opt, h2);\n      \t}\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n      \t\t\toutput_commit_title(opt, iter->item);\n      \t}\n      \n    +-\tnum_merge_bases = commit_list_count(ca);\n     -\tmerged_common_ancestors = pop_commit(&ca);\n     -\tif (merged_common_ancestors == NULL) {\n    ++\tnum_merge_bases = commit_list_count(merge_bases);\n     +\tmerged_merge_bases = pop_commit(&merge_bases);\n     +\tif (merged_merge_bases == NULL) {\n      \t\t/* if there is no common ancestor, use an empty tree */\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n      \t\t\treturn err(opt, _(\"merge returned no commit\"));\n      \t}\n      \n    +@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n    + \t\tbreak;\n    + \tcase 1:\n    + \t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n    +-\t\t\t\t\t &merged_common_ancestors->object.oid,\n    ++\t\t\t\t\t &merged_merge_bases->object.oid,\n    + \t\t\t\t\t DEFAULT_ABBREV);\n    + \t\topt->ancestor = merge_base_abbrev.buf;\n    + \t\tbreak;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n      \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     -\t\t\t\t\t\t\t  merged_common_ancestors),\n     +\t\t\t\t\t\t\t  merged_merge_bases),\n      \t\t\t\t     &mrtree);\n    + \tstrbuf_release(&merge_base_abbrev);\n      \tif (clean < 0) {\n    - \t\tflush_output(opt);\n     @@ merge-recursive.c: static void merge_finalize(struct merge_options *opt)\n      int merge_trees(struct merge_options *opt,\n      \t\tstruct tree *head,\n    @@ merge-recursive.c: static void merge_finalize(struct merge_options *opt)\n      {\n      \tint clean;\n      \tstruct tree *ignored;\n    +@@ merge-recursive.c: int merge_trees(struct merge_options *opt,\n      \n      \tif (merge_start(opt, head))\n      \t\treturn -1;\n    @@ merge-recursive.c: int merge_trees(struct merge_options *opt,\n      \t\t    struct commit **result)\n      {\n      \tint clean;\n    +@@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \n      \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n      \t\treturn -1;\n10:  daee364ce1 ! 15:  93a3ce6b88 merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     -\tstruct tree *mrtree;\n     +\tstruct tree *result_tree;\n      \tint clean;\n    - \n    - \tif (show(opt, 4)) {\n    + \tint num_merge_bases;\n    + \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n      \t\t\t\t     repo_get_commit_tree(opt->repo,\n      \t\t\t\t\t\t\t  merged_merge_bases),\n     -\t\t\t\t     &mrtree);\n     +\t\t\t\t     &result_tree);\n    + \tstrbuf_release(&merge_base_abbrev);\n      \tif (clean < 0) {\n      \t\tflush_output(opt);\n    - \t\treturn clean;\n    +@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t}\n      \n      \tif (opt->call_depth) {\n11:  50a7a6f671 = 16:  1b1df10c11 merge-recursive: rename merge_options argument to opt in header\n12:  4e9e774dc5 = 17:  1526977a85 merge-recursive: move some definitions around to clean up the header\n13:  bf40502fd8 ! 18:  c90f2f15cd merge-recursive: consolidate unnecessary fields in merge_options\n    @@ merge-recursive.c: void init_merge_options(struct merge_options *opt,\n     -\topt->diff_detect_rename = -1;\n     -\topt->merge_detect_rename = -1;\n     +\topt->detect_renames = -1;\n    - \topt->detect_directory_renames = 1;\n    + \topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n      \tmerge_recursive_config(opt);\n      \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n     @@ merge-recursive.c: int parse_merge_opt(struct merge_options *opt, const char *s)\n    @@ merge-recursive.c: int parse_merge_opt(struct merge_options *opt, const char *s)\n     \n      ## merge-recursive.h ##\n     @@ merge-recursive.h: struct merge_options {\n    - \tlong xdl_opts;\n    - \tint verbosity;\n    - \tint detect_directory_renames;\n    + \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n    + \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n    + \t} detect_directory_renames;\n     -\tint diff_detect_rename;\n     -\tint merge_detect_rename;\n     -\tint diff_rename_limit;\n14:  2c39a4be36 ! 19:  6d930dba72 merge-recursive: comment and reorder the merge_options fields\n    @@ merge-recursive.c: void init_merge_options(struct merge_options *opt,\n      \topt->repo = repo;\n     +\n     +\topt->detect_renames = -1;\n    -+\topt->detect_directory_renames = 1;\n    ++\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n     +\topt->rename_limit = -1;\n     +\n      \topt->verbosity = 2;\n    @@ merge-recursive.c: void init_merge_options(struct merge_options *opt,\n     +\n      \topt->renormalize = 0;\n     -\topt->detect_renames = -1;\n    --\topt->detect_directory_renames = 1;\n    +-\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n     +\n     +\tstring_list_init(&opt->df_conflict_file_set, 1);\n     +\n    @@ merge-recursive.h: struct commit;\n      \tconst char *ancestor;\n      \tconst char *branch1;\n      \tconst char *branch2;\n    +-\tenum {\n    +-\t\tMERGE_RECURSIVE_NORMAL = 0,\n    +-\t\tMERGE_RECURSIVE_OURS,\n    +-\t\tMERGE_RECURSIVE_THEIRS\n    +-\t} recursive_variant;\n    +-\tconst char *subtree_shift;\n    +-\tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n    +-\tunsigned renormalize : 1;\n    +-\tlong xdl_opts;\n    +-\tint verbosity;\n     +\n     +\t/* rename related options */\n     +\tint detect_renames;\n    -+\tint detect_directory_renames;\n    -+\tint rename_limit;\n    -+\tint rename_score;\n    -+\tint show_rename_progress;\n    + \tenum {\n    + \t\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n    + \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n    + \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n    + \t} detect_directory_renames;\n    +-\tint detect_renames;\n    + \tint rename_limit;\n    + \tint rename_score;\n    +-\tint needed_rename_limit;\n    + \tint show_rename_progress;\n     +\n     +\t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n     +\tlong xdl_opts;\n    - \tenum {\n    - \t\tMERGE_RECURSIVE_NORMAL = 0,\n    - \t\tMERGE_RECURSIVE_OURS,\n    - \t\tMERGE_RECURSIVE_THEIRS\n    - \t} recursive_variant;\n    --\tconst char *subtree_shift;\n    ++\tenum {\n    ++\t\tMERGE_RECURSIVE_NORMAL = 0,\n    ++\t\tMERGE_RECURSIVE_OURS,\n    ++\t\tMERGE_RECURSIVE_THEIRS\n    ++\t} recursive_variant;\n     +\n     +\t/* console output related options */\n     +\tint verbosity;\n    - \tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n    ++\tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n     +\tstruct strbuf obuf;     /* output buffer */\n     +\n     +\t/* miscellaneous control options */\n     +\tconst char *subtree_shift;\n    - \tunsigned renormalize : 1;\n    --\tlong xdl_opts;\n    --\tint verbosity;\n    --\tint detect_directory_renames;\n    --\tint detect_renames;\n    --\tint rename_limit;\n    --\tint rename_score;\n    --\tint needed_rename_limit;\n    --\tint show_rename_progress;\n    ++\tunsigned renormalize : 1;\n     +\n     +\t/* internal fields used by the implementation (do NOT set these) */\n      \tint call_depth;\n15:  c1c71816eb = 20:  ec3e15f6a8 merge-recursive: avoid losing output and leaking memory holding that output\n16:  be47a6bfdf ! 21:  7edfac7048 merge-recursive: split internal fields into a separate struct\n    @@ merge-recursive.c: static int merge_submodule(struct merge_options *opt,\n      \t/* store a in result in case we fail */\n      \toidcpy(result, a);\n     @@ merge-recursive.c: static int handle_rename_via_dir(struct merge_options *opt,\n    - \tint mark_conflicted = (opt->detect_directory_renames == 1);\n    + \t\t\t       MERGE_DIRECTORY_RENAMES_CONFLICT);\n      \tassert(ren->dir_rename_original_dest);\n      \n     -\tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n    @@ merge-recursive.c: static struct diff_queue_struct *get_diffpairs(struct merge_o\n      \n      \tret = xmalloc(sizeof(*ret));\n      \t*ret = diff_queued_diff;\n    +@@ merge-recursive.c: static int detect_and_process_renames(struct merge_options *opt,\n    + \n    + \tif ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) ||\n    + \t    (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n    +-\t     !opt->call_depth)) {\n    ++\t     !opt->priv->call_depth)) {\n    + \t\tdir_re_head = get_directory_renames(head_pairs);\n    + \t\tdir_re_merge = get_directory_renames(merge_pairs);\n    + \n     @@ merge-recursive.c: static int handle_content_merge(struct merge_file_info *mfi,\n      \t\treason = _(\"add/add\");\n      \n    @@ merge-recursive.c: static int merge_trees_internal(struct merge_options *opt,\n      \n      \tunpack_trees_finish(opt);\n      \n    --\tif (opt->call_depth) {\n    -+\tif (opt->priv->call_depth) {\n    - \t\tstruct object_id tree_id;\n    - \t\tif (write_index_as_tree(&tree_id, opt->repo->index, NULL,\n    - \t\t\t\t\tWRITE_TREE_FROM_MEMORY, NULL) ||\n    +-\tif (opt->call_depth &&\n    ++\tif (opt->priv->call_depth &&\n    + \t    !(*result = write_inmemory_index_as_tree(opt->repo)))\n    + \t\treturn -1;\n    + \n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \n      \tfor (iter = merge_bases; iter; iter = iter->next) {\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     +\tif (!opt->priv->call_depth)\n      \t\trepo_read_index(opt->repo);\n      \n    - \topt->ancestor = \"merged common ancestors\";\n    + \tswitch (num_merge_bases) {\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\treturn clean;\n      \t}\n18:  40161dc352 = 22:  9a381873c2 merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n -:  ---------- > 23:  c6bc8a196f merge-recursive: add sanity checks for relevant merge_options\n17:  f440ee1e64 = 24:  2123e9e4e4 merge-recursive: alphabetize include list\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380495","messageId":"20190815214053.16594-2-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 01/24] merge-recursive: be consistent with assert","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:30Z","receivedAt":"2019-08-15T21:41:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 8daec1df03de (\"merge-recursive: switch from (oid,mode) pairs\nto a diff_filespec\", 2019-04-05), an assertion on a->path && b->path\nwas added for code readability to document that these both needed to be\nnon-NULL at this point in the code.  However, the subsequent lines also\nread o->path, so it should be included in the assert.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 6b812d67e3..1d960fa64b 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1043,7 +1043,7 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path);\n+\tassert(a->path && b->path && o->path);\n \tif (strcmp(a->path, b->path) ||\n \t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n \t\tbase_name = opt->ancestor == NULL ? NULL :\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380496","messageId":"20190815214053.16594-4-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 03/24] merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:32Z","receivedAt":"2019-08-15T21:41:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We always want our conflict hunks to be labelled so that users can know\nwhere each came from.  The previous commit fixed the one caller in the\ncodebase which was not setting opt->ancestor (and thus not providing a\nlabel for the \"merge base\" conflict hunk in diff3-style conflict\nmarkers); add an assertion to prevent future codepaths from also\noverlooking this requirement.\n\nEnforcing this requirement also allows us to simplify the code for\nlabelling the conflict hunks by no longer checking if the ancestor label\nis NULL.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 19 +++++++++----------\n 1 file changed, 9 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1d960fa64b..a67ea4957a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1019,7 +1019,7 @@ static int merge_3way(struct merge_options *opt,\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n-\tchar *base_name, *name1, *name2;\n+\tchar *base, *name1, *name2;\n \tint merge_status;\n \n \tll_opts.renormalize = opt->renormalize;\n@@ -1043,16 +1043,13 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path && o->path);\n-\tif (strcmp(a->path, b->path) ||\n-\t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s:%s\", opt->ancestor, o->path);\n+\tassert(a->path && b->path && o->path && opt->ancestor);\n+\tif (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {\n+\t\tbase  = mkpathdup(\"%s:%s\", opt->ancestor, o->path);\n \t\tname1 = mkpathdup(\"%s:%s\", branch1, a->path);\n \t\tname2 = mkpathdup(\"%s:%s\", branch2, b->path);\n \t} else {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s\", opt->ancestor);\n+\t\tbase  = mkpathdup(\"%s\", opt->ancestor);\n \t\tname1 = mkpathdup(\"%s\", branch1);\n \t\tname2 = mkpathdup(\"%s\", branch2);\n \t}\n@@ -1061,11 +1058,11 @@ static int merge_3way(struct merge_options *opt,\n \tread_mmblob(&src1, &a->oid);\n \tread_mmblob(&src2, &b->oid);\n \n-\tmerge_status = ll_merge(result_buf, a->path, &orig, base_name,\n+\tmerge_status = ll_merge(result_buf, a->path, &orig, base,\n \t\t\t\t&src1, name1, &src2, name2,\n \t\t\t\topt->repo->index, &ll_opts);\n \n-\tfree(base_name);\n+\tfree(base);\n \tfree(name1);\n \tfree(name2);\n \tfree(orig.ptr);\n@@ -3390,6 +3387,8 @@ int merge_trees(struct merge_options *opt,\n \tint code, clean;\n \tstruct strbuf sb = STRBUF_INIT;\n \n+\tassert(opt->ancestor != NULL);\n+\n \tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n \t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n \t\t    sb.buf);\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380497","messageId":"20190815214053.16594-3-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 02/24] checkout: provide better conflict hunk description with detached HEAD","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:31Z","receivedAt":"2019-08-15T21:41:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When running 'git checkout -m' and using diff3 style conflict markers,\nwe want all the conflict hunks (left-side, \"common\" or \"merge base\", and\nright-side) to have label markers letting the user know where each came\nfrom.  The \"common\" hunk label (o.ancestor) came from\nold_branch_info->name, but that is NULL when HEAD is detached, which\nresulted in a blank label.  Check for that case and provide an\nabbreviated commit hash instead.\n\n(Incidentally, this was the only case in the git codebase where\nmerge_trees() was called with opt->ancestor being NULL.  A subsequent\ncommit will prevent similar problems by enforcing that merge_trees()\nalways be called with opt->ancestor != NULL.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6123f732a2..d5b946dc3a 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -713,6 +713,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\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@@ -768,6 +769,12 @@ static int merge_working_tree(const struct checkout_opts *opts,\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 == NULL) {\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\tret = merge_trees(&o,\n@@ -781,6 +788,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t opts, 0,\n \t\t\t\t\t writeout_error);\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-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380498","messageId":"20190815214053.16594-5-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 04/24] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:33Z","receivedAt":"2019-08-15T21:41:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 7ca56aa07619 (\"merge-recursive: add a label for ancestor\",\n2010-03-20), a label was added for the '||||||' line to make it have\nthe more informative heading '|||||| merged common ancestors', with\nthe statement:\n\n    It would be nicer to use a more informative label.  Perhaps someone\n    will provide one some day.\n\nThis chosen label was perfectly reasonable when recursiveness kicks in,\ni.e. when there are multiple merge bases.  (I can't think of a better\nlabel in such cases.)  But it is actually somewhat misleading when there\nis a unique merge base or no merge base.  Change this based on the\nnumber of merge bases:\n    >=2: \"merged common ancestors\"\n    1:   <abbreviated commit hash>\n    0:   \"<empty tree>\"\n\nTests have also been added to check that we get the right ancestor name\nfor each of the three cases.\n\nAlso, since merge_recursive() and merge_trees() have polar opposite\npre-conditions for opt->ancestor, document merge_recursive()'s\npre-condition with an assertion.  (An assertion was added to\nmerge_trees() already a few commits ago.)  The differences in\npre-conditions stem from two factors: (1) merge_trees() does not recurse\nand thus does not have multiple sub-merges to worry about -- each of\nwhich would require a different value for opt->ancestor, (2)\nmerge_trees() is only passed trees rather than commits and thus cannot\ninternally guess as good of a label.  Thus, while external callers of\nmerge_trees() are required to provide a non-NULL opt->ancestor,\nmerge_recursive() expects to set this value itself.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |  21 +++-\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 189 ++++++++++++++++++++++++++++++\n 3 files changed, 214 insertions(+), 4 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a67ea4957a..3a7a9514b9 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3507,6 +3507,11 @@ int merge_recursive(struct merge_options *opt,\n \tstruct commit *merged_common_ancestors;\n \tstruct tree *mrtree;\n \tint clean;\n+\tint num_merge_bases;\n+\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n+\n+\tif (!opt->call_depth)\n+\t\tassert(opt->ancestor == NULL);\n \n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n@@ -3528,6 +3533,7 @@ int merge_recursive(struct merge_options *opt,\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n+\tnum_merge_bases = commit_list_count(ca);\n \tmerged_common_ancestors = pop_commit(&ca);\n \tif (merged_common_ancestors == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n@@ -3568,10 +3574,23 @@ int merge_recursive(struct merge_options *opt,\n \tif (!opt->call_depth)\n \t\trepo_read_index(opt->repo);\n \n-\topt->ancestor = \"merged common ancestors\";\n+\tswitch (num_merge_bases) {\n+\tcase 0:\n+\t\topt->ancestor = \"<empty tree>\";\n+\t\tbreak;\n+\tcase 1:\n+\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n+\t\t\t\t\t &merged_common_ancestors->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\topt->ancestor = merge_base_abbrev.buf;\n+\t\tbreak;\n+\tdefault:\n+\t\topt->ancestor = \"merged common ancestors\";\n+\t}\n \tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n \t\t\t    get_commit_tree(merged_common_ancestors),\n \t\t\t    &mrtree);\n+\tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d23b948f27..7fddcc8c73 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1562,6 +1562,7 @@ test_expect_success 'check nested conflicts' '\n \t\tcd nested_conflicts &&\n \n \t\tgit clean -f &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L2^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1582,7 +1583,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:a >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1594,7 +1595,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:b >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1732,6 +1733,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t(\n \t\tcd virtual_merge_base_has_nested_conflicts &&\n \n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L3^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1760,7 +1762,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t\tcp left merged-once &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tmerged-once \\\n \t\t\tbase        \\\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nnew file mode 100755\nindex 0000000000..f69c8256bc\n--- /dev/null\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -0,0 +1,189 @@\n+#!/bin/sh\n+\n+test_description='recursive merge diff3 style conflict markers'\n+\n+. ./test-lib.sh\n+\n+# Setup:\n+#          L1\n+#            \\\n+#             ?\n+#            /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 both have a file named 'content' but have no common history\n+#\n+\n+test_expect_success 'setup no merge base' '\n+\ttest_create_repo no_merge_base &&\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout -b L &&\n+\t\ttest_commit A content A &&\n+\n+\t\tgit checkout --orphan R &&\n+\t\ttest_commit B content B\n+\t)\n+'\n+\n+test_expect_success 'check no merge base' '\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| <empty tree>\" content\n+\t)\n+'\n+\n+# Setup:\n+#          L1\n+#         /  \\\n+#   master    ?\n+#         \\  /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 have modified the same file ('content') in conflicting ways\n+#\n+\n+test_expect_success 'setup unique merge base' '\n+\ttest_create_repo unique_merge_base &&\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\ttest_commit base content \"1\n+2\n+3\n+4\n+5\n+\" &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\tgit checkout L &&\n+\t\ttest_commit L content \"1\n+2\n+3\n+4\n+5\n+7\" &&\n+\n+\t\tgit checkout R &&\n+\t\tgit rm content &&\n+\t\ttest_commit R renamed \"1\n+2\n+3\n+4\n+5\n+six\"\n+\t)\n+'\n+\n+test_expect_success 'check unique merge base' '\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| $MASTER:content\" renamed\n+\t)\n+'\n+\n+# Setup:\n+#          L1---L2--L3\n+#         /  \\ /      \\\n+#   master    X1       ?\n+#         \\  / \\      /\n+#          R1---R2--R3\n+#\n+# Where:\n+#   commits L1 and R1 have modified the same file in non-conflicting ways\n+#   X1 is an auto-generated merge-base used when merging L1 and R1\n+#   commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively\n+#   commits L3 and R3 both modify 'content' in conflicting ways\n+#\n+\n+test_expect_success 'setup multiple merge bases' '\n+\ttest_create_repo multiple_merge_bases &&\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\ttest_commit initial content \"1\n+2\n+3\n+4\n+5\" &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\ttest_commit L1 content \"0\n+1\n+2\n+3\n+4\n+5\" &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\ttest_commit R1 content \"1\n+2\n+3\n+4\n+5\n+6\" &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\tgit merge R1 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\tgit merge L1 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_commit L3 content \"0\n+1\n+2\n+3\n+4\n+5\n+A\" &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\tgit rm content &&\n+\t\ttest_commit R3 renamed \"0\n+2\n+3\n+4\n+5\n+six\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple merge bases' '\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| merged common ancestors:content\" renamed\n+\t)\n+'\n+\n+test_done\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380499","messageId":"20190815214053.16594-6-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 05/24] merge-recursive: introduce an enum for detect_directory_renames values","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:34Z","receivedAt":"2019-08-15T21:41:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nImprove code readability by introducing an enum to replace the\nnot-quite-boolean values taken on by detect_directory_renames.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/am.c      |  2 +-\n merge-recursive.c | 24 +++++++++++++++---------\n merge-recursive.h |  6 +++++-\n 3 files changed, 21 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1aea657a7f..037e828efe 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1538,7 +1538,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \to.branch1 = \"HEAD\";\n \ttheir_tree_name = xstrfmt(\"%.*s\", linelen(state->msg), state->msg);\n \to.branch2 = their_tree_name;\n-\to.detect_directory_renames = 0;\n+\to.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;\n \n \tif (state->quiet)\n \t\to.verbosity = 0;\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3a7a9514b9..dd2ee5edee 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1375,7 +1375,8 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \tconst struct rename *ren = ci->ren1;\n \tconst struct diff_filespec *dest = ren->pair->two;\n \tchar *file_path = dest->path;\n-\tint mark_conflicted = (opt->detect_directory_renames == 1);\n+\tint mark_conflicted = (opt->detect_directory_renames ==\n+\t\t\t       MERGE_DIRECTORY_RENAMES_CONFLICT);\n \tassert(ren->dir_rename_original_dest);\n \n \tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n@@ -2860,8 +2861,9 @@ static int detect_and_process_renames(struct merge_options *opt,\n \thead_pairs = get_diffpairs(opt, common, head);\n \tmerge_pairs = get_diffpairs(opt, common, merge);\n \n-\tif ((opt->detect_directory_renames == 2) ||\n-\t    (opt->detect_directory_renames == 1 && !opt->call_depth)) {\n+\tif ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) ||\n+\t    (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n+\t     !opt->call_depth)) {\n \t\tdir_re_head = get_directory_renames(head_pairs);\n \t\tdir_re_merge = get_directory_renames(merge_pairs);\n \n@@ -3119,7 +3121,8 @@ static int handle_rename_normal(struct merge_options *opt,\n \tclean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),\n \t\t\t\t     o, a, b, ci);\n \n-\tif (clean && opt->detect_directory_renames == 1 &&\n+\tif (clean &&\n+\t    opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n \t    ren->dir_rename_original_dest) {\n \t\tif (update_stages(opt, path,\n \t\t\t\t  NULL,\n@@ -3164,12 +3167,12 @@ static int warn_about_dir_renamed_entries(struct merge_options *opt,\n \t\treturn clean;\n \n \t/* Sanity checks */\n-\tassert(opt->detect_directory_renames > 0);\n+\tassert(opt->detect_directory_renames > MERGE_DIRECTORY_RENAMES_NONE);\n \tassert(ren->dir_rename_original_type == 'A' ||\n \t       ren->dir_rename_original_type == 'R');\n \n \t/* Check whether to treat directory renames as a conflict */\n-\tclean = (opt->detect_directory_renames == 2);\n+\tclean = (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE);\n \n \tis_add = (ren->dir_rename_original_type == 'A');\n \tif (ren->dir_rename_original_type == 'A' && clean) {\n@@ -3684,9 +3687,12 @@ static void merge_recursive_config(struct merge_options *opt)\n \tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n \t\tint boolval = git_parse_maybe_bool(value);\n \t\tif (0 <= boolval) {\n-\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n+\t\t\topt->detect_directory_renames = boolval ?\n+\t\t\t\tMERGE_DIRECTORY_RENAMES_TRUE :\n+\t\t\t\tMERGE_DIRECTORY_RENAMES_NONE;\n \t\t} else if (!strcasecmp(value, \"conflict\")) {\n-\t\t\topt->detect_directory_renames = 1;\n+\t\t\topt->detect_directory_renames =\n+\t\t\t\tMERGE_DIRECTORY_RENAMES_CONFLICT;\n \t\t} /* avoid erroring on values from future versions of git */\n \t\tfree(value);\n \t}\n@@ -3706,7 +3712,7 @@ void init_merge_options(struct merge_options *opt,\n \topt->renormalize = 0;\n \topt->diff_detect_rename = -1;\n \topt->merge_detect_rename = -1;\n-\topt->detect_directory_renames = 1;\n+\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c2b7bb65c6..f1b6ef38ae 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -22,7 +22,11 @@ struct merge_options {\n \tunsigned renormalize : 1;\n \tlong xdl_opts;\n \tint verbosity;\n-\tint detect_directory_renames;\n+\tenum {\n+\t\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n+\t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n+\t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n+\t} detect_directory_renames;\n \tint diff_detect_rename;\n \tint merge_detect_rename;\n \tint diff_rename_limit;\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380500","messageId":"20190815214053.16594-7-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 06/24] merge-recursive: future-proof update_file_flags() against memory leaks","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:35Z","receivedAt":"2019-08-15T21:41:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There is a 'free_buf' label to which all but one of the error paths in\nupdate_file_flags() jump; that error case involves a NULL buf and is\nthus not a memory leak.  However, make that error case execute the same\ndeallocation code anyway so that if anyone adds any additional memory\nallocations or deallocations, then all error paths correctly deallocate\nresources.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex dd2ee5edee..eae3e4fbcc 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -934,9 +934,11 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \n \t\tbuf = read_object_file(&contents->oid, &type, &size);\n-\t\tif (!buf)\n-\t\t\treturn err(opt, _(\"cannot read object %s '%s'\"),\n-\t\t\t\t   oid_to_hex(&contents->oid), path);\n+\t\tif (!buf) {\n+\t\t\tret = err(opt, _(\"cannot read object %s '%s'\"),\n+\t\t\t\t  oid_to_hex(&contents->oid), path);\n+\t\t\tgoto free_buf;\n+\t\t}\n \t\tif (type != OBJ_BLOB) {\n \t\t\tret = err(opt, _(\"blob expected for %s '%s'\"),\n \t\t\t\t  oid_to_hex(&contents->oid), path);\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380501","messageId":"20190815214053.16594-8-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 07/24] merge-recursive: remove another implicit dependency on the_repository","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:36Z","receivedAt":"2019-08-15T21:41:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Commit d7cf3a96e9a0 (\"merge-recursive.c: remove implicit dependency on\nthe_repository\", 2019-01-12) and follow-ups like commit 34e7771bc644\n(\"Use the right 'struct repository' instead of the_repository\",\n2019-06-27), removed most implicit uses of the_repository.  Convert\ncalls to get_commit_tree() to instead use repo_get_commit_tree() to get\nrid of another.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex eae3e4fbcc..fb668fc84c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3592,8 +3592,11 @@ int merge_recursive(struct merge_options *opt,\n \tdefault:\n \t\topt->ancestor = \"merged common ancestors\";\n \t}\n-\tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n-\t\t\t    get_commit_tree(merged_common_ancestors),\n+\tclean = merge_trees(opt,\n+\t\t\t    repo_get_commit_tree(opt->repo, h1),\n+\t\t\t    repo_get_commit_tree(opt->repo, h2),\n+\t\t\t    repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t merged_common_ancestors),\n \t\t\t    &mrtree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380502","messageId":"20190815214053.16594-9-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 08/24] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:37Z","receivedAt":"2019-08-15T21:41:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is the bug that just won't die; there always seems to be another\nform of it somewhere.  See the commit message of 55f39cf7551b (\"merge:\nfix misleading pre-merge check documentation\", 2018-06-30) for a more\ndetailed explanation), but in short:\n\n<quick summary>\n\nbuiltin/merge.c contains this important requirement for merge\nstrategies:\n\n    ...the index must be in sync with the head commit.  The strategies are\n    responsible to ensure this.\n\nThis condition is important to enforce because there are two likely\nfailure cases when the index isn't in sync with the head commit:\n\n  * we silently throw away changes the user had staged before the merge\n\n  * we accidentally (and silently) include changes in the merge that\n    were not part of either of the branches/trees being merged\n\nDiscarding users' work and mis-merging are both bad outcomes, especially\nwhen done silently, so naturally this rule was stated sternly -- but,\nunfortunately totally ignored in practice unless and until actual bugs\nwere found.  But, fear not: the bugs from this were fixed in commit\n  ee6566e8d70d (\"[PATCH] Rewrite read-tree\", 2005-09-05)\nthrough a rewrite of read-tree (again, commit 55f39cf7551b has a more\ndetailed explanation of how this affected merge).  And it was fixed\nagain in commit\n  160252f81626 (\"git-merge-ours: make sure our index matches HEAD\", 2005-11-03)\n...and it was fixed again in commit\n  3ec62ad9ffba (\"merge-octopus: abort if index does not match HEAD\", 2016-04-09)\n...and again in commit\n  65170c07d466 (\"merge-recursive: avoid incorporating uncommitted changes in a merge\", 2017-12-21)\n...and again in commit\n  eddd1a411d93 (\"merge-recursive: enforce rule that index matches head before merging\", 2018-06-30)\n\n...with multiple testcases added to the testsuite that could be\nenumerated in even more commits.\n\nThen, finally, in a patch in the same series as the last fix above, the\ndocumentation about this requirement was fixed in commit 55f39cf7551b\n(\"merge: fix misleading pre-merge check documentation\", 2018-06-30), and\nwe all lived happily ever after...\n\n</quick summary>\n\nUnfortunately, \"ever after\" apparently denotes a limited time and it\nexpired today.  The merge-recursive rule to enforce that index matches\nhead was at the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially HUGE amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nTrying to enforce that all of merge_trees(), merge_recursive(), and\nmerge_recursive_generic() checked the index == head condition earlier\nresulted in a bunch of broken tests.  It turns out that\nmerge_recursive() has code to drop and reload the cache while recursing\nto create intermediate virtual merge bases, but unfortunately that code\nruns even when no recursion is necessary.  This unconditional dropping\nand reloading of the cache masked a few bugs:\n\n  * builtin/merge-recursive.c: didn't even bother loading the index.\n\n  * builtin/stash.c: feels like a fake 'builtin' because it repeatedly\n    invokes git subprocesses all over the place, mixed with other\n    operations.  In particular, invoking \"git reset\" will reset the\n    index on disk, but the parent process that invoked it won't\n    automatically have its in-memory index updated.\n\n  * t3030-merge-recursive.h: this test has always been broken in that it\n    didn't make sure to make index match head before running.  But, it\n    didn't care about the index or even the merge result, just the\n    verbose output while running.  While commit eddd1a411d93\n    (\"merge-recursive: enforce rule that index matches head before\n    merging\", 2018-06-30) should have uncovered this broken test, it\n    used a test_must_fail wrapper around the merge-recursive call\n    because it was known that the merge resulted in a rename/rename\n    conflict.  Thus, that fix only made this test fail for a different\n    reason, and since the index == head check didn't happen until after\n    coming all the way back out of the recursion, the testcase had\n    enough information to pass the one check that it did perform.\n\nSo, load the index in builtin/merge-recursive.c, reload the in-memory\nindex in builtin/stash.c, and modify the t3030 testcase to correctly\nsetup the index and make sure that the test fails in the expected way\n(meaning it reports a rename/rename conflict).  This makes sure that\nall callers actually make the index match head.  The next commit will\nthen enforce the condition that index matches head earlier so this\nproblem doesn't return in the future.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-recursive.c  | 4 ++++\n builtin/stash.c            | 2 ++\n t/t3030-merge-recursive.sh | 9 ++++++++-\n 3 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex 5b910e351e..a4bfd8fc51 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -1,3 +1,4 @@\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n #include \"tag.h\"\n@@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n \t\tdie(_(\"not handling anything other than two heads merge.\"));\n \n+\tif (repo_read_index_unmerged(the_repository))\n+\t\tdie_resolve_conflict(\"merge\");\n+\n \to.branch1 = argv[++i];\n \to.branch2 = argv[++i];\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex b5a301f24d..4aa47785f9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \t\t\t\treturn error(_(\"could not save index tree\"));\n \n \t\t\treset_head();\n+\t\t\tdiscard_cache();\n+\t\t\tread_cache();\n \t\t}\n \t}\n \ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex ff641b348a..a37bcc58a0 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '\n test_expect_success 'merge-recursive remembers the names of all base trees' '\n \tgit reset --hard HEAD &&\n \n+\t# make the index match $c1 so that merge-recursive below does not\n+\t# fail early\n+\tgit diff --binary HEAD $c1 -- | git apply --cached &&\n+\n \t# more trees than static slots used by oid_to_hex()\n \tfor commit in $c0 $c2 $c4 $c5 $c6 $c7\n \tdo\n \t\tgit rev-parse \"$commit^{tree}\"\n \tdone >trees &&\n \n-\t# ignore the return code -- it only fails because the input is weird\n+\t# ignore the return code; it only fails because the input is weird...\n \ttest_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&\n \n+\t# ...but make sure it fails in the expected way\n+\ttest_i18ngrep CONFLICT.*rename/rename out &&\n+\n \t# merge-recursive prints in reverse order, but we do not care\n \tsort <trees >expect &&\n \tsed -n \"s/^virtual //p\" out | sort >actual &&\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380503","messageId":"20190815214053.16594-10-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 09/24] merge-recursive: exit early if index != head","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:38Z","receivedAt":"2019-08-15T21:41:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We had a rule to enforce that the index matches head, but it was found\nat the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially huge amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nFurther, not enforcing this requirement earlier allowed other bugs (such\nas an unintentional unconditional dropping and reloading of the index in\nmerge_recursive() even when no recursion was necessary), to mask bugs in\nother callers (which were fixed in the commit prior to this one).\n\nMake sure we do the index == head check at the beginning of the merge,\nand error out immediately if it fails.  While we're at it, fix a small\nleak in the show-the-error codepath.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 101 +++++++++++++++++++++++++++++++++-------------\n 1 file changed, 72 insertions(+), 29 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fb668fc84c..ded2e1bcfb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3382,23 +3382,14 @@ static int process_entry(struct merge_options *opt,\n \treturn clean_merge;\n }\n \n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+static int merge_trees_internal(struct merge_options *opt,\n+\t\t\t\tstruct tree *head,\n+\t\t\t\tstruct tree *merge,\n+\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tassert(opt->ancestor != NULL);\n-\n-\tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n-\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n-\t\t    sb.buf);\n-\t\treturn -1;\n-\t}\n \n \tif (opt->subtree_shift) {\n \t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n@@ -3502,11 +3493,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n  * Merge the commits h1 and h2, return the resulting virtual\n  * commit object and a flag indicating the cleanness of the merge.\n  */\n-int merge_recursive(struct merge_options *opt,\n-\t\t    struct commit *h1,\n-\t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n-\t\t    struct commit **result)\n+static int merge_recursive_internal(struct merge_options *opt,\n+\t\t\t\t    struct commit *h1,\n+\t\t\t\t    struct commit *h2,\n+\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_common_ancestors;\n@@ -3515,9 +3506,6 @@ int merge_recursive(struct merge_options *opt,\n \tint num_merge_bases;\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n-\tif (!opt->call_depth)\n-\t\tassert(opt->ancestor == NULL);\n-\n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n \t\toutput_commit_title(opt, h1);\n@@ -3564,7 +3552,7 @@ int merge_recursive(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive(opt, merged_common_ancestors, iter->item,\n+\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n \t\t\t\t    NULL, &merged_common_ancestors) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n@@ -3592,12 +3580,12 @@ int merge_recursive(struct merge_options *opt,\n \tdefault:\n \t\topt->ancestor = \"merged common ancestors\";\n \t}\n-\tclean = merge_trees(opt,\n-\t\t\t    repo_get_commit_tree(opt->repo, h1),\n-\t\t\t    repo_get_commit_tree(opt->repo, h2),\n-\t\t\t    repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t merged_common_ancestors),\n-\t\t\t    &mrtree);\n+\tclean = merge_trees_internal(opt,\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n+\t\t\t\t     repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t     &mrtree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n@@ -3618,6 +3606,61 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n+static int merge_start(struct merge_options *opt, struct tree *head)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merge_finalize(struct merge_options *opt)\n+{\n+\t/* Common code for wrapping up merges will be added here later */\n+}\n+\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *common,\n+\t\tstruct tree **result)\n+{\n+\tint clean;\n+\n+\tassert(opt->ancestor != NULL);\n+\n+\tif (merge_start(opt, head))\n+\t\treturn -1;\n+\tclean = merge_trees_internal(opt, head, merge, common, result);\n+\tmerge_finalize(opt);\n+\n+\treturn clean;\n+}\n+\n+int merge_recursive(struct merge_options *opt,\n+\t\t    struct commit *h1,\n+\t\t    struct commit *h2,\n+\t\t    struct commit_list *ca,\n+\t\t    struct commit **result)\n+{\n+\tint clean;\n+\n+\tassert(opt->ancestor == NULL);\n+\n+\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n+\t\treturn -1;\n+\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tmerge_finalize(opt);\n+\n+\treturn clean;\n+}\n+\n static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n \t\t\t      const char *name)\n {\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380504","messageId":"20190815214053.16594-11-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 10/24] merge-recursive: remove useless parameter in merge_trees()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:39Z","receivedAt":"2019-08-15T21:41:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees() took a results parameter that would only be written when\nopt->call_depth was positive, which is never the case now that\nmerge_trees_internal() has been split from merge_trees().  Remove the\nmisleading and unused parameter from merge_trees().\n\nWhile at it, add some comments explaining how the output of\nmerge_trees() and merge_recursive() differ.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  4 +---\n merge-recursive.c  |  6 +++---\n merge-recursive.h  | 20 ++++++++++++++++----\n sequencer.c        |  4 ++--\n 4 files changed, 22 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex d5b946dc3a..90e0eaf25e 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -708,7 +708,6 @@ static int merge_working_tree(const struct checkout_opts *opts,\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 *result;\n \t\t\tstruct tree *work;\n \t\t\tstruct tree *old_tree;\n \t\t\tstruct merge_options o;\n@@ -780,8 +779,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = merge_trees(&o,\n \t\t\t\t\t  new_tree,\n \t\t\t\t\t  work,\n-\t\t\t\t\t  old_tree,\n-\t\t\t\t\t  &result);\n+\t\t\t\t\t  old_tree);\n \t\t\tif (ret < 0)\n \t\t\t\texit(128);\n \t\t\tret = reset_tree(new_tree,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ded2e1bcfb..89e9a7518c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3628,16 +3628,16 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+\t\tstruct tree *common)\n {\n \tint clean;\n+\tstruct tree *ignored;\n \n \tassert(opt->ancestor != NULL);\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tclean = merge_trees_internal(opt, head, merge, common, result);\n+\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n \tmerge_finalize(opt);\n \n \treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex f1b6ef38ae..18012fff9d 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -74,19 +74,31 @@ static inline int merge_detect_rename(struct merge_options *o)\n \t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n }\n \n-/* merge_trees() but with recursive ancestor consolidation */\n+/*\n+ * merge_recursive is like merge_trees() but with recursive ancestor\n+ * consolidation, and when successful, it creates an actual commit\n+ * and writes its address to *result.\n+ *\n+ * NOTE: empirically, about a decade ago it was determined that with more\n+ *       than two merge bases, optimal behavior was found when the\n+ *       ancestors were passed in the order of oldest merge base to newest\n+ *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n+ *       you need it.\n+ */\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *ancestors,\n \t\t    struct commit **result);\n \n-/* rename-detecting three-way merge, no recursion */\n+/*\n+ * rename-detecting three-way merge, no recursion; result of merge is written\n+ * to opt->repo->index.\n+ */\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result);\n+\t\tstruct tree *common);\n \n /*\n  * \"git-merge-recursive\" can be fed trees; wrap them into\ndiff --git a/sequencer.c b/sequencer.c\nindex 34ebf8ed94..c4ed30f1b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -586,7 +586,7 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n-\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n \tint clean;\n \tchar **xopt;\n \tstruct lock_file index_lock = LOCK_INIT;\n@@ -613,7 +613,7 @@ static int do_recursive_merge(struct repository *r,\n \n \tclean = merge_trees(&o,\n \t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree, &result);\n+\t\t\t    next_tree, base_tree);\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380505","messageId":"20190815214053.16594-12-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 11/24] merge-recursive: don't force external callers to do our logging","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:40Z","receivedAt":"2019-08-15T21:41:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Alternatively, you can view this as \"make the merge functions behave\nmore similarly.\"  merge-recursive has three different entry points:\nmerge_trees(), merge_recursive(), and merge_recursive_generic().  Two of\nthese would call diff_warn_rename_limit(), but merge_trees() didn't.\nThis lead to callers of merge_trees() needing to manually call\ndiff_warn_rename_limit() themselves.  Move this to the new\nmerge_finalize() function to make sure that all three entry points run\nthis function.\n\nNote that there are two external callers of merge_trees(), one in\nsequencer.c and one in builtin/checkout.c.  The one in sequencer.c is\ncleaned up by this patch and just transfers where the call to\ndiff_warn_rename_limit() is made; the one in builtin/checkout.c is for\nswitching to a different commit and in the very rare case where the\nwarning might be triggered, it would probably be helpful to include\n(e.g. if someone is modifying a file that has been renamed in moving to\nthe other commit, but there are so many renames between the commits that\nthe limit kicks in and none are detected, it may help to have an\nexplanation about why they got a delete/modify conflict instead of a\nproper content merge in a renamed file).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++----\n sequencer.c       | 1 -\n 2 files changed, 3 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 89e9a7518c..6cee867d37 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3600,9 +3600,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tflush_output(opt);\n \tif (!opt->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n-\tif (show(opt, 2))\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n \treturn clean;\n }\n \n@@ -3622,7 +3619,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n-\t/* Common code for wrapping up merges will be added here later */\n+\tif (show(opt, 2))\n+\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t\t       opt->needed_rename_limit, 0);\n }\n \n int merge_trees(struct merge_options *opt,\ndiff --git a/sequencer.c b/sequencer.c\nindex c4ed30f1b4..094a4dd03d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -617,7 +617,6 @@ static int do_recursive_merge(struct repository *r,\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-\tdiff_warn_rename_limit(\"merge.renamelimit\", o.needed_rename_limit, 0);\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380506","messageId":"20190815214053.16594-16-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 15/24] merge-recursive: rename 'mrtree' to 'result_tree', for clarity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:44Z","receivedAt":"2019-08-15T21:41:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"It is not at all clear what 'mr' was supposed to stand for, at least not\nto me.  Pick a clearer name for this variable.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d0153ba971..bffb5eac7d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3477,7 +3477,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_merge_bases;\n-\tstruct tree *mrtree;\n+\tstruct tree *result_tree;\n \tint clean;\n \tint num_merge_bases;\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n@@ -3562,7 +3562,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n \t\t\t\t\t\t\t  merged_merge_bases),\n-\t\t\t\t     &mrtree);\n+\t\t\t\t     &result_tree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n@@ -3570,7 +3570,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t}\n \n \tif (opt->call_depth) {\n-\t\t*result = make_virtual_commit(opt->repo, mrtree, \"merged tree\");\n+\t\t*result = make_virtual_commit(opt->repo, result_tree,\n+\t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380507","messageId":"20190815214053.16594-15-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 14/24] merge-recursive: use common name for ancestors/common/base_list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:43Z","receivedAt":"2019-08-15T21:41:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees(), merge_recursive(), and merge_recursive_generic() in\ntheir function headers used four different names for the merge base or\nlist of merge bases they were passed:\n  * 'common'\n  * 'ancestors'\n  * 'ca'\n  * 'base_list'\nThey were able to refer to it four different ways instead of only three\nby using a different name in the signature for the .c file than the .h\nfile.  Change all of these to 'merge_base' or 'merge_bases'.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 72 ++++++++++++++++++++++++-----------------------\n merge-recursive.h | 14 ++++-----\n 2 files changed, 44 insertions(+), 42 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 40f2c6ad55..d0153ba971 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3357,24 +3357,26 @@ static int process_entry(struct merge_options *opt,\n static int merge_trees_internal(struct merge_options *opt,\n \t\t\t\tstruct tree *head,\n \t\t\t\tstruct tree *merge,\n-\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree *merge_base,\n \t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n \n \tif (opt->subtree_shift) {\n-\t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n-\t\tcommon = shift_tree_object(opt->repo, head, common, opt->subtree_shift);\n+\t\tmerge = shift_tree_object(opt->repo, head, merge,\n+\t\t\t\t\t  opt->subtree_shift);\n+\t\tmerge_base = shift_tree_object(opt->repo, head, merge_base,\n+\t\t\t\t\t       opt->subtree_shift);\n \t}\n \n-\tif (oid_eq(&common->object.oid, &merge->object.oid)) {\n+\tif (oid_eq(&merge_base->object.oid, &merge->object.oid)) {\n \t\toutput(opt, 0, _(\"Already up to date!\"));\n \t\t*result = head;\n \t\treturn 1;\n \t}\n \n-\tcode = unpack_trees_start(opt, common, head, merge);\n+\tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(opt, 4) || opt->call_depth)\n@@ -3402,7 +3404,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tget_files_dirs(opt, merge);\n \n \t\tentries = get_unmerged(opt->repo->index);\n-\t\tclean = detect_and_process_renames(opt, common, head, merge,\n+\t\tclean = detect_and_process_renames(opt, merge_base, head, merge,\n \t\t\t\t\t\t   entries, &re_info);\n \t\trecord_df_conflict_files(opt, entries);\n \t\tif (clean < 0)\n@@ -3470,11 +3472,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t    struct commit *h1,\n \t\t\t\t    struct commit *h2,\n-\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit_list *merge_bases,\n \t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n-\tstruct commit *merged_common_ancestors;\n+\tstruct commit *merged_merge_bases;\n \tstruct tree *mrtree;\n \tint clean;\n \tint num_merge_bases;\n@@ -3486,32 +3488,32 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\toutput_commit_title(opt, h2);\n \t}\n \n-\tif (!ca) {\n-\t\tca = get_merge_bases(h1, h2);\n-\t\tca = reverse_commit_list(ca);\n+\tif (!merge_bases) {\n+\t\tmerge_bases = get_merge_bases(h1, h2);\n+\t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \n \tif (show(opt, 5)) {\n-\t\tunsigned cnt = commit_list_count(ca);\n+\t\tunsigned cnt = commit_list_count(merge_bases);\n \n \t\toutput(opt, 5, Q_(\"found %u common ancestor:\",\n \t\t\t\t\"found %u common ancestors:\", cnt), cnt);\n-\t\tfor (iter = ca; iter; iter = iter->next)\n+\t\tfor (iter = merge_bases; iter; iter = iter->next)\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n-\tnum_merge_bases = commit_list_count(ca);\n-\tmerged_common_ancestors = pop_commit(&ca);\n-\tif (merged_common_ancestors == NULL) {\n+\tnum_merge_bases = commit_list_count(merge_bases);\n+\tmerged_merge_bases = pop_commit(&merge_bases);\n+\tif (merged_merge_bases == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n-\t\t\t\t\t\t\t      tree, \"ancestor\");\n+\t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n+\t\t\t\t\t\t\t \"ancestor\");\n \t}\n \n-\tfor (iter = ca; iter; iter = iter->next) {\n+\tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n \t\topt->call_depth++;\n \t\t/*\n@@ -3527,14 +3529,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n-\t\t\t\t    NULL, &merged_common_ancestors) < 0)\n+\t\tif (merge_recursive_internal(opt, merged_merge_bases, iter->item,\n+\t\t\t\t\t     NULL, &merged_merge_bases) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->call_depth--;\n \n-\t\tif (!merged_common_ancestors)\n+\t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n@@ -3548,7 +3550,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tbreak;\n \tcase 1:\n \t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n-\t\t\t\t\t &merged_common_ancestors->object.oid,\n+\t\t\t\t\t &merged_merge_bases->object.oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \t\topt->ancestor = merge_base_abbrev.buf;\n \t\tbreak;\n@@ -3559,7 +3561,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &mrtree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n@@ -3602,7 +3604,7 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common)\n+\t\tstruct tree *merge_base)\n {\n \tint clean;\n \tstruct tree *ignored;\n@@ -3611,7 +3613,7 @@ int merge_trees(struct merge_options *opt,\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n+\tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n \tmerge_finalize(opt);\n \n \treturn clean;\n@@ -3620,7 +3622,7 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result)\n {\n \tint clean;\n@@ -3629,7 +3631,7 @@ int merge_recursive(struct merge_options *opt,\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n-\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n \tmerge_finalize(opt);\n \n \treturn clean;\n@@ -3657,8 +3659,8 @@ static struct commit *get_ref(struct repository *repo,\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_base_list,\n-\t\t\t    const struct object_id **base_list,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result)\n {\n \tint clean;\n@@ -3667,14 +3669,14 @@ int merge_recursive_generic(struct merge_options *opt,\n \tstruct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);\n \tstruct commit_list *ca = NULL;\n \n-\tif (base_list) {\n+\tif (merge_bases) {\n \t\tint i;\n-\t\tfor (i = 0; i < num_base_list; ++i) {\n+\t\tfor (i = 0; i < num_merge_bases; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n-\t\t\t\t\t     oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, merge_bases[i],\n+\t\t\t\t\t     oid_to_hex(merge_bases[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n-\t\t\t\t\t   oid_to_hex(base_list[i]));\n+\t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t}\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 0a3033bdb0..6f351098a5 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -81,14 +81,14 @@ static inline int merge_detect_rename(struct merge_options *o)\n  *\n  * NOTE: empirically, about a decade ago it was determined that with more\n  *       than two merge bases, optimal behavior was found when the\n- *       ancestors were passed in the order of oldest merge base to newest\n- *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n- *       you need it.\n+ *       merge_bases were passed in the order of oldest commit to newest\n+ *       commit.  Also, merge_bases will be consumed (emptied) so make a\n+ *       copy if you need it.\n  */\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ancestors,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /*\n@@ -98,7 +98,7 @@ int merge_recursive(struct merge_options *o,\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common);\n+\t\tstruct tree *merge_base);\n \n /*\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n@@ -107,8 +107,8 @@ int merge_trees(struct merge_options *o,\n int merge_recursive_generic(struct merge_options *o,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_ca,\n-\t\t\t    const struct object_id **ca,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n void init_merge_options(struct merge_options *o,\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380508","messageId":"20190815214053.16594-14-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 13/24] merge-recursive: fix some overly long lines","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:42Z","receivedAt":"2019-08-15T21:41:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code change, just add some line breaks to fix lines that\nhave grown in length due to various refactorings.  Most remaining lines\nof excessive length in merge-recursive include error messages and it's\nnot clear that splitting those improves things.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 19 +++++++++++++------\n 1 file changed, 13 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1fbae52402..40f2c6ad55 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -681,7 +681,9 @@ static void add_flattened_path(struct strbuf *out, const char *s)\n \t\t\tout->buf[i] = '_';\n }\n \n-static char *unique_path(struct merge_options *opt, const char *path, const char *branch)\n+static char *unique_path(struct merge_options *opt,\n+\t\t\t const char *path,\n+\t\t\t const char *branch)\n {\n \tstruct path_hashmap_entry *entry;\n \tstruct strbuf newpath = STRBUF_INIT;\n@@ -915,7 +917,8 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \t\tif (S_ISREG(contents->mode)) {\n \t\t\tstruct strbuf strbuf = STRBUF_INIT;\n-\t\t\tif (convert_to_working_tree(opt->repo->index, path, buf, size, &strbuf)) {\n+\t\t\tif (convert_to_working_tree(opt->repo->index,\n+\t\t\t\t\t\t    path, buf, size, &strbuf)) {\n \t\t\t\tfree(buf);\n \t\t\t\tsize = strbuf.len;\n \t\t\t\tbuf = strbuf_detach(&strbuf, NULL);\n@@ -3393,7 +3396,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp, NULL, 512);\n+\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n \n@@ -3503,7 +3507,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n+\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n+\t\t\t\t\t\t\t      tree, \"ancestor\");\n \t}\n \n \tfor (iter = ca; iter; iter = iter->next) {\n@@ -3630,7 +3635,8 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n-static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n+static struct commit *get_ref(struct repository *repo,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const char *name)\n {\n \tstruct object *object;\n@@ -3665,7 +3671,8 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\tint i;\n \t\tfor (i = 0; i < num_base_list; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i], oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n+\t\t\t\t\t     oid_to_hex(base_list[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n \t\t\t\t\t   oid_to_hex(base_list[i]));\n \t\t\tcommit_list_insert(base, &ca);\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380509","messageId":"20190815214053.16594-13-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 12/24] cache-tree: share code between functions writing an index as a tree","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:41Z","receivedAt":"2019-08-15T21:41:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"write_tree_from_memory() appeared to be a merge-recursive special that\nbasically duplicated write_index_as_tree().  The two have a different\nsignature, but the bigger difference was just that write_index_as_tree()\nwould always unconditionally read the index off of disk instead of\nworking on the current in-memory index.  So:\n\n  * split out common code into write_index_as_tree_internal()\n\n  * rename write_tree_from_memory() to write_inmemory_index_as_tree(),\n    make it call write_index_as_tree_internal(), and move it to\n    cache-tree.c\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  2 +-\n cache-tree.c       | 81 +++++++++++++++++++++++++++++++++-------------\n cache-tree.h       |  3 +-\n merge-recursive.c  | 34 ++-----------------\n merge-recursive.h  |  1 -\n 5 files changed, 63 insertions(+), 58 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 90e0eaf25e..19a3a0bae5 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -760,7 +760,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t */\n \t\t\tinit_merge_options(&o, the_repository);\n \t\t\to.verbosity = 0;\n-\t\t\twork = write_tree_from_memory(&o);\n+\t\t\twork = write_inmemory_index_as_tree(the_repository);\n \n \t\t\tret = reset_tree(new_tree,\n \t\t\t\t\t opts, 1,\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 706ffcf188..00eda3e537 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -608,11 +608,62 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat\n \treturn it;\n }\n \n+int write_index_as_tree_internal(struct object_id *oid, struct index_state *index_state, int cache_tree_valid, int flags, const char *prefix)\n+{\n+\tif (flags & WRITE_TREE_IGNORE_CACHE_TREE) {\n+\t\tcache_tree_free(&index_state->cache_tree);\n+\t\tcache_tree_valid = 0;\n+\t}\n+\n+\tif (!index_state->cache_tree)\n+\t\tindex_state->cache_tree = cache_tree();\n+\n+\tif (!cache_tree_valid && cache_tree_update(index_state, flags) < 0)\n+\t\treturn WRITE_TREE_UNMERGED_INDEX;\n+\n+\tif (prefix) {\n+\t\tstruct cache_tree *subtree;\n+\t\tsubtree = cache_tree_find(index_state->cache_tree, prefix);\n+\t\tif (!subtree)\n+\t\t\treturn WRITE_TREE_PREFIX_ERROR;\n+\t\toidcpy(oid, &subtree->oid);\n+\t}\n+\telse\n+\t\toidcpy(oid, &index_state->cache_tree->oid);\n+\n+\treturn 0;\n+}\n+\n+struct tree* write_inmemory_index_as_tree(struct repository *repo) {\n+\tstruct object_id o;\n+\tint was_valid, ret;\n+\n+\tstruct index_state *index_state\t= repo->index;\n+\twas_valid = index_state->cache_tree &&\n+\t\t    cache_tree_fully_valid(index_state->cache_tree);\n+\n+\tret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL);\n+\tif (ret == WRITE_TREE_UNMERGED_INDEX) {\n+\t\tint i;\n+\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n+\t\tfor (i = 0; i < index_state->cache_nr; i++) {\n+\t\t\tconst struct cache_entry *ce = index_state->cache[i];\n+\t\t\tif (ce_stage(ce))\n+\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n+\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n+\t\t}\n+\t\tBUG(\"unmerged index entries when writing inmemory index\");\n+\t}\n+\n+\treturn lookup_tree(repo, &index_state->cache_tree->oid);\n+}\n+\n+\n int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix)\n {\n \tint entries, was_valid;\n \tstruct lock_file lock_file = LOCK_INIT;\n-\tint ret = 0;\n+\tint ret;\n \n \thold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);\n \n@@ -621,18 +672,14 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \t\tret = WRITE_TREE_UNREADABLE_INDEX;\n \t\tgoto out;\n \t}\n-\tif (flags & WRITE_TREE_IGNORE_CACHE_TREE)\n-\t\tcache_tree_free(&index_state->cache_tree);\n \n-\tif (!index_state->cache_tree)\n-\t\tindex_state->cache_tree = cache_tree();\n+\twas_valid = !(flags & WRITE_TREE_IGNORE_CACHE_TREE) &&\n+\t\t    index_state->cache_tree &&\n+\t\t    cache_tree_fully_valid(index_state->cache_tree);\n \n-\twas_valid = cache_tree_fully_valid(index_state->cache_tree);\n-\tif (!was_valid) {\n-\t\tif (cache_tree_update(index_state, flags) < 0) {\n-\t\t\tret = WRITE_TREE_UNMERGED_INDEX;\n-\t\t\tgoto out;\n-\t\t}\n+\tret = write_index_as_tree_internal(oid, index_state, was_valid, flags,\n+\t\t\t\t\t   prefix);\n+\tif (!ret && !was_valid) {\n \t\twrite_locked_index(index_state, &lock_file, COMMIT_LOCK);\n \t\t/* Not being able to write is fine -- we are only interested\n \t\t * in updating the cache-tree part, and if the next caller\n@@ -642,18 +689,6 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \t\t */\n \t}\n \n-\tif (prefix) {\n-\t\tstruct cache_tree *subtree;\n-\t\tsubtree = cache_tree_find(index_state->cache_tree, prefix);\n-\t\tif (!subtree) {\n-\t\t\tret = WRITE_TREE_PREFIX_ERROR;\n-\t\t\tgoto out;\n-\t\t}\n-\t\toidcpy(oid, &subtree->oid);\n-\t}\n-\telse\n-\t\toidcpy(oid, &index_state->cache_tree->oid);\n-\n out:\n \trollback_lock_file(&lock_file);\n \treturn ret;\ndiff --git a/cache-tree.h b/cache-tree.h\nindex 757bbc48bc..8ce1632bc6 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -34,7 +34,7 @@ int cache_tree_fully_valid(struct cache_tree *);\n int cache_tree_update(struct index_state *, int);\n void cache_tree_verify(struct repository *, struct index_state *);\n \n-/* bitmasks to write_cache_as_tree flags */\n+/* bitmasks to write_index_as_tree flags */\n #define WRITE_TREE_MISSING_OK 1\n #define WRITE_TREE_IGNORE_CACHE_TREE 2\n #define WRITE_TREE_DRY_RUN 4\n@@ -46,6 +46,7 @@ void cache_tree_verify(struct repository *, struct index_state *);\n #define WRITE_TREE_UNMERGED_INDEX (-2)\n #define WRITE_TREE_PREFIX_ERROR (-3)\n \n+struct tree* write_inmemory_index_as_tree(struct repository *repo);\n int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix);\n void prime_cache_tree(struct repository *, struct index_state *, struct tree *);\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 6cee867d37..1fbae52402 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -412,37 +412,6 @@ static void unpack_trees_finish(struct merge_options *opt)\n \tclear_unpack_trees_porcelain(&opt->unpack_opts);\n }\n \n-struct tree *write_tree_from_memory(struct merge_options *opt)\n-{\n-\tstruct tree *result = NULL;\n-\tstruct index_state *istate = opt->repo->index;\n-\n-\tif (unmerged_index(istate)) {\n-\t\tint i;\n-\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n-\t\tfor (i = 0; i < istate->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = istate->cache[i];\n-\t\t\tif (ce_stage(ce))\n-\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n-\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n-\t\t}\n-\t\tBUG(\"unmerged index entries in merge-recursive.c\");\n-\t}\n-\n-\tif (!istate->cache_tree)\n-\t\tistate->cache_tree = cache_tree();\n-\n-\tif (!cache_tree_fully_valid(istate->cache_tree) &&\n-\t    cache_tree_update(istate, 0) < 0) {\n-\t\terr(opt, _(\"error building trees\"));\n-\t\treturn NULL;\n-\t}\n-\n-\tresult = lookup_tree(opt->repo, &istate->cache_tree->oid);\n-\n-\treturn result;\n-}\n-\n static int save_files_dirs(const struct object_id *oid,\n \t\t\t   struct strbuf *base, const char *path,\n \t\t\t   unsigned int mode, int stage, void *context)\n@@ -3472,7 +3441,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth && !(*result = write_tree_from_memory(opt)))\n+\tif (opt->call_depth &&\n+\t    !(*result = write_inmemory_index_as_tree(opt->repo)))\n \t\treturn -1;\n \n \treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 18012fff9d..0a3033bdb0 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -113,7 +113,6 @@ int merge_recursive_generic(struct merge_options *o,\n \n void init_merge_options(struct merge_options *o,\n \t\t\tstruct repository *repo);\n-struct tree *write_tree_from_memory(struct merge_options *o);\n \n int parse_merge_opt(struct merge_options *out, const char *s);\n \n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380510","messageId":"20190815214053.16594-17-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 16/24] merge-recursive: rename merge_options argument to opt in header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:45Z","receivedAt":"2019-08-15T21:41:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 259ccb6cc324 (\"merge-recursive: rename merge_options argument\nfrom 'o' to 'opt'\", 2019-04-05), I renamed a bunch of function\narguments in merge-recursive.c, but forgot to make that same change to\nmerge-recursive.h.  Make the two match.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.h | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 6f351098a5..2cb3844ad9 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -68,10 +68,10 @@ struct collision_entry {\n \tunsigned reported_already:1;\n };\n \n-static inline int merge_detect_rename(struct merge_options *o)\n+static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn o->merge_detect_rename >= 0 ? o->merge_detect_rename :\n-\t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n }\n \n /*\n@@ -85,7 +85,7 @@ static inline int merge_detect_rename(struct merge_options *o)\n  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n  *       copy if you need it.\n  */\n-int merge_recursive(struct merge_options *o,\n+int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *merge_bases,\n@@ -95,7 +95,7 @@ int merge_recursive(struct merge_options *o,\n  * rename-detecting three-way merge, no recursion; result of merge is written\n  * to opt->repo->index.\n  */\n-int merge_trees(struct merge_options *o,\n+int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *merge_base);\n@@ -104,16 +104,16 @@ int merge_trees(struct merge_options *o,\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n  * virtual commits and call merge_recursive() proper.\n  */\n-int merge_recursive_generic(struct merge_options *o,\n+int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *o,\n+void init_merge_options(struct merge_options *opt,\n \t\t\tstruct repository *repo);\n \n-int parse_merge_opt(struct merge_options *out, const char *s);\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n \n #endif\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380511","messageId":"20190815214053.16594-18-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 17/24] merge-recursive: move some definitions around to clean up the header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:46Z","receivedAt":"2019-08-15T21:41:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code changes (view this with diff --color-moved), but\na few small code cleanups:\n  * Move structs and an inline function only used by merge-recursive.c\n    into merge-recursive.c\n  * Re-order function declarations to be more logical\n  * Add or fix some explanatory comments\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 31 +++++++++++++++++\n merge-recursive.h | 87 +++++++++++++++++++++++------------------------\n 2 files changed, 73 insertions(+), 45 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex bffb5eac7d..e401114b8f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -54,6 +54,24 @@ static unsigned int path_hash(const char *path)\n \treturn ignore_case ? strihash(path) : strhash(path);\n }\n \n+/*\n+ * For dir_rename_entry, directory names are stored as a full path from the\n+ * toplevel of the repository and do not include a trailing '/'.  Also:\n+ *\n+ *   dir:                original name of directory being renamed\n+ *   non_unique_new_dir: if true, could not determine new_dir\n+ *   new_dir:            final name of directory being renamed\n+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n+ *                       in get_directory_renames() for details\n+ */\n+struct dir_rename_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *dir;\n+\tunsigned non_unique_new_dir:1;\n+\tstruct strbuf new_dir;\n+\tstruct string_list possible_new_dirs;\n+};\n+\n static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t      char *dir)\n {\n@@ -92,6 +110,13 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,\n \tstring_list_init(&entry->possible_new_dirs, 0);\n }\n \n+struct collision_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *target_file;\n+\tstruct string_list source_files;\n+\tunsigned reported_already:1;\n+};\n+\n static struct collision_entry *collision_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t    char *target_file)\n {\n@@ -358,6 +383,12 @@ static int add_cacheinfo(struct merge_options *opt,\n \treturn ret;\n }\n \n+static inline int merge_detect_rename(struct merge_options *opt)\n+{\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+}\n+\n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n {\n \tparse_tree(tree);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 2cb3844ad9..0fdae904dd 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -43,47 +43,50 @@ struct merge_options {\n \tstruct repository *repo;\n };\n \n+void init_merge_options(struct merge_options *opt, struct repository *repo);\n+\n+/* parse the option in s and update the relevant field of opt */\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n+\n /*\n- * For dir_rename_entry, directory names are stored as a full path from the\n- * toplevel of the repository and do not include a trailing '/'.  Also:\n- *\n- *   dir:                original name of directory being renamed\n- *   non_unique_new_dir: if true, could not determine new_dir\n- *   new_dir:            final name of directory being renamed\n- *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n- *                       in get_directory_renames() for details\n+ * RETURN VALUES: All the merge_* functions below return a value as follows:\n+ *   > 0     Merge was clean\n+ *   = 0     Merge had conflicts\n+ *   < 0     Merge hit an unexpected and unrecoverable problem (e.g. disk\n+ *             full) and aborted merge part-way through.\n  */\n-struct dir_rename_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *dir;\n-\tunsigned non_unique_new_dir:1;\n-\tstruct strbuf new_dir;\n-\tstruct string_list possible_new_dirs;\n-};\n-\n-struct collision_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *target_file;\n-\tstruct string_list source_files;\n-\tunsigned reported_already:1;\n-};\n \n-static inline int merge_detect_rename(struct merge_options *opt)\n-{\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n-}\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - No commit is created\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is not updated\n+ *   - The working tree is updated with results of the merge\n+ */\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *merge_base);\n \n /*\n  * merge_recursive is like merge_trees() but with recursive ancestor\n- * consolidation, and when successful, it creates an actual commit\n- * and writes its address to *result.\n+ * consolidation and, if the commit is clean, creation of a commit.\n  *\n  * NOTE: empirically, about a decade ago it was determined that with more\n  *       than two merge bases, optimal behavior was found when the\n  *       merge_bases were passed in the order of oldest commit to newest\n  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n  *       copy if you need it.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - If merge is clean, a commit is created and its address written to *result\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is not updated\n+ *   - The working tree is updated with results of the merge\n  */\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n@@ -92,17 +95,16 @@ int merge_recursive(struct merge_options *opt,\n \t\t    struct commit **result);\n \n /*\n- * rename-detecting three-way merge, no recursion; result of merge is written\n- * to opt->repo->index.\n- */\n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *merge_base);\n-\n-/*\n- * \"git-merge-recursive\" can be fed trees; wrap them into\n- * virtual commits and call merge_recursive() proper.\n+ * merge_recursive_generic can operate on trees instead of commits, by\n+ * wrapping the trees into virtual commits, and calling merge_recursive().\n+ * It also writes out the in-memory index to disk if the merge is successful.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - If merge is clean, a commit is created and its address written to *result\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is updated\n+ *   - The working tree is updated with results of the merge\n  */\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n@@ -111,9 +113,4 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *opt,\n-\t\t\tstruct repository *repo);\n-\n-int parse_merge_opt(struct merge_options *opt, const char *s);\n-\n #endif\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380513","messageId":"20190815214053.16594-20-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 19/24] merge-recursive: comment and reorder the merge_options fields","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:48Z","receivedAt":"2019-08-15T21:41:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The merge_options struct had lots of fields, making it a little\nimposing, but the options naturally fall into multiple different groups.\nGrouping similar options and adding a comment or two makes it easier to\nread, easier for new folks to figure out which options are related, and\nthus easier for them to find the options they need.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 16 +++++++++++-----\n merge-recursive.h | 40 ++++++++++++++++++++++++++--------------\n 2 files changed, 37 insertions(+), 19 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b846acf931..db2c544d0b 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3759,21 +3759,27 @@ void init_merge_options(struct merge_options *opt,\n {\n \tconst char *merge_verbosity;\n \tmemset(opt, 0, sizeof(struct merge_options));\n+\n \topt->repo = repo;\n+\n+\topt->detect_renames = -1;\n+\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\topt->rename_limit = -1;\n+\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->rename_limit = -1;\n+\tstrbuf_init(&opt->obuf, 0);\n+\n \topt->renormalize = 0;\n-\topt->detect_renames = -1;\n-\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\n+\tstring_list_init(&opt->df_conflict_file_set, 1);\n+\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\n \t\topt->verbosity = strtol(merge_verbosity, NULL, 10);\n \tif (opt->verbosity >= 5)\n \t\topt->buffer_output = 0;\n-\tstrbuf_init(&opt->obuf, 0);\n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n }\n \n int parse_merge_opt(struct merge_options *opt, const char *s)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex f4bdfbc897..9e040608fe 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -9,36 +9,48 @@ struct commit;\n struct repository;\n \n struct merge_options {\n+\tstruct repository *repo;\n+\n+\t/* ref names used in console messages and conflict markers */\n \tconst char *ancestor;\n \tconst char *branch1;\n \tconst char *branch2;\n-\tenum {\n-\t\tMERGE_RECURSIVE_NORMAL = 0,\n-\t\tMERGE_RECURSIVE_OURS,\n-\t\tMERGE_RECURSIVE_THEIRS\n-\t} recursive_variant;\n-\tconst char *subtree_shift;\n-\tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n-\tunsigned renormalize : 1;\n-\tlong xdl_opts;\n-\tint verbosity;\n+\n+\t/* rename related options */\n+\tint detect_renames;\n \tenum {\n \t\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n \t} detect_directory_renames;\n-\tint detect_renames;\n \tint rename_limit;\n \tint rename_score;\n-\tint needed_rename_limit;\n \tint show_rename_progress;\n+\n+\t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n+\tlong xdl_opts;\n+\tenum {\n+\t\tMERGE_RECURSIVE_NORMAL = 0,\n+\t\tMERGE_RECURSIVE_OURS,\n+\t\tMERGE_RECURSIVE_THEIRS\n+\t} recursive_variant;\n+\n+\t/* console output related options */\n+\tint verbosity;\n+\tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n+\tstruct strbuf obuf;     /* output buffer */\n+\n+\t/* miscellaneous control options */\n+\tconst char *subtree_shift;\n+\tunsigned renormalize : 1;\n+\n+\t/* internal fields used by the implementation (do NOT set these) */\n \tint call_depth;\n-\tstruct strbuf obuf;\n+\tint needed_rename_limit;\n \tstruct hashmap current_file_dir_set;\n \tstruct string_list df_conflict_file_set;\n \tstruct unpack_trees_options unpack_opts;\n \tstruct index_state orig_index;\n-\tstruct repository *repo;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380512","messageId":"20190815214053.16594-21-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 20/24] merge-recursive: avoid losing output and leaking memory holding that output","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:49Z","receivedAt":"2019-08-15T21:41:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If opt->buffer_output is less than 2, then merge_trees(),\nmerge_recursive(), and merge_recursive_generic() are all supposed to\nflush the opt->obuf output buffer to stdout and release any memory it\nholds.  merge_trees() did not do this.  Move the logic that handles this\nfor merge_recursive_internal() to merge_finalize() so that all three\nmethods handle this requirement.\n\nNote that this bug didn't cause any problems currently, because there\nare only two callers of merge_trees() right now (a git grep for\n'merge_trees(' is misleading because builtin/merge-tree.c also defines a\n'merge_tree' function that is unrelated), and only one of those is\ncalled with buffer_output less than 2 (builtin/checkout.c), but it set\nopt->verbosity to 0, for which there is only currently one non-error\nmessage that would be shown: \"Already up to date!\".  However, that one\nmessage can only occur when the merge is utterly trivial (the merge base\ntree exactly matches the merge tree), and builtin/checkout.c already\nattempts a trivial merge via unpack_trees() before falling back to\nmerge_trees().\n\nAlso, if opt->buffer_output is 2, then the caller is responsible to\nhandle showing any output in opt->obuf and for free'ing it.  This\nrequirement might be easy to overlook, so add a comment to\nmerge-recursive.h pointing it out.  (There are currently two callers\nthat set buffer_output to 2, both in sequencer.c, and both of which\nhandle this correctly.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 6 +++---\n merge-recursive.h | 3 ++-\n 2 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex db2c544d0b..b4334d0506 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3603,9 +3603,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-\tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n-\t\tstrbuf_release(&opt->obuf);\n \treturn clean;\n }\n \n@@ -3625,6 +3622,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n+\tflush_output(opt);\n+\tif (!opt->call_depth && opt->buffer_output < 2)\n+\t\tstrbuf_release(&opt->obuf);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opt->needed_rename_limit, 0);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 9e040608fe..933d6e7642 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -38,7 +38,8 @@ struct merge_options {\n \t/* console output related options */\n \tint verbosity;\n \tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n-\tstruct strbuf obuf;     /* output buffer */\n+\tstruct strbuf obuf;     /* output buffer; if buffer_output == 2, caller\n+\t\t\t\t * must handle and call strbuf_release */\n \n \t/* miscellaneous control options */\n \tconst char *subtree_shift;\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380514","messageId":"20190815214053.16594-25-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 24/24] merge-recursive: alphabetize include list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:53Z","receivedAt":"2019-08-15T21:41:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Other than cache.h which needs to appear first, and merge-recursive.h\nwhich I want to be second so that we are more likely to notice if\nmerge-recursive.h has any missing includes, the rest of the list is\nlong and easier to look through if it's alphabetical.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 33 +++++++++++++++++----------------\n 1 file changed, 17 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex bc0da608c4..d3dc3d8a49 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -4,30 +4,31 @@\n  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006\n  */\n #include \"cache.h\"\n-#include \"config.h\"\n+#include \"merge-recursive.h\"\n+\n #include \"advice.h\"\n-#include \"lockfile.h\"\n-#include \"cache-tree.h\"\n-#include \"object-store.h\"\n-#include \"repository.h\"\n-#include \"commit.h\"\n+#include \"alloc.h\"\n+#include \"attr.h\"\n #include \"blob.h\"\n #include \"builtin.h\"\n-#include \"tree-walk.h\"\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"commit-reach.h\"\n+#include \"config.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"dir.h\"\n+#include \"ll-merge.h\"\n+#include \"lockfile.h\"\n+#include \"object-store.h\"\n+#include \"repository.h\"\n+#include \"revision.h\"\n+#include \"string-list.h\"\n+#include \"submodule.h\"\n #include \"tag.h\"\n-#include \"alloc.h\"\n+#include \"tree-walk.h\"\n #include \"unpack-trees.h\"\n-#include \"string-list.h\"\n #include \"xdiff-interface.h\"\n-#include \"ll-merge.h\"\n-#include \"attr.h\"\n-#include \"merge-recursive.h\"\n-#include \"dir.h\"\n-#include \"submodule.h\"\n-#include \"revision.h\"\n-#include \"commit-reach.h\"\n \n struct merge_options_internal {\n \tint call_depth;\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380515","messageId":"20190815214053.16594-22-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 21/24] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:50Z","receivedAt":"2019-08-15T21:41:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_options has several internal fields that should not be set or read\nby external callers.  This just complicates the API.  Move them into an\nopaque merge_options_internal struct that is defined only in\nmerge-recursive.c and keep these out of merge-recursive.h.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 186 ++++++++++++++++++++++++----------------------\n merge-recursive.h |  17 ++---\n 2 files changed, 106 insertions(+), 97 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b4334d0506..840b09f1dc 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -29,6 +29,15 @@\n #include \"revision.h\"\n #include \"commit-reach.h\"\n \n+struct merge_options_internal {\n+\tint call_depth;\n+\tint needed_rename_limit;\n+\tstruct hashmap current_file_dir_set;\n+\tstruct string_list df_conflict_file_set;\n+\tstruct unpack_trees_options unpack_opts;\n+\tstruct index_state orig_index;\n+};\n+\n struct path_hashmap_entry {\n \tstruct hashmap_entry e;\n \tchar path[FLEX_ARRAY];\n@@ -309,7 +318,8 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \n static int show(struct merge_options *opt, int v)\n {\n-\treturn (!opt->call_depth && opt->verbosity >= v) || opt->verbosity >= 5;\n+\treturn (!opt->priv->call_depth && opt->verbosity >= v) ||\n+\t\topt->verbosity >= 5;\n }\n \n __attribute__((format (printf, 3, 4)))\n@@ -320,7 +330,7 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)\n \tif (!show(opt, v))\n \t\treturn;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \n \tva_start(ap, fmt);\n \tstrbuf_vaddf(&opt->obuf, fmt, ap);\n@@ -335,7 +345,7 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit\n {\n \tstruct merge_remote_desc *desc;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \tdesc = merge_remote_util(commit);\n \tif (desc)\n \t\tstrbuf_addf(&opt->obuf, \"virtual %s\\n\", desc->name);\n@@ -403,43 +413,43 @@ static int unpack_trees_start(struct merge_options *opt,\n \tstruct tree_desc t[3];\n \tstruct index_state tmp_index = { NULL };\n \n-\tmemset(&opt->unpack_opts, 0, sizeof(opt->unpack_opts));\n-\tif (opt->call_depth)\n-\t\topt->unpack_opts.index_only = 1;\n+\tmemset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));\n+\tif (opt->priv->call_depth)\n+\t\topt->priv->unpack_opts.index_only = 1;\n \telse\n-\t\topt->unpack_opts.update = 1;\n-\topt->unpack_opts.merge = 1;\n-\topt->unpack_opts.head_idx = 2;\n-\topt->unpack_opts.fn = threeway_merge;\n-\topt->unpack_opts.src_index = opt->repo->index;\n-\topt->unpack_opts.dst_index = &tmp_index;\n-\topt->unpack_opts.aggressive = !merge_detect_rename(opt);\n-\tsetup_unpack_trees_porcelain(&opt->unpack_opts, \"merge\");\n+\t\topt->priv->unpack_opts.update = 1;\n+\topt->priv->unpack_opts.merge = 1;\n+\topt->priv->unpack_opts.head_idx = 2;\n+\topt->priv->unpack_opts.fn = threeway_merge;\n+\topt->priv->unpack_opts.src_index = opt->repo->index;\n+\topt->priv->unpack_opts.dst_index = &tmp_index;\n+\topt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);\n+\tsetup_unpack_trees_porcelain(&opt->priv->unpack_opts, \"merge\");\n \n \tinit_tree_desc_from_tree(t+0, common);\n \tinit_tree_desc_from_tree(t+1, head);\n \tinit_tree_desc_from_tree(t+2, merge);\n \n-\trc = unpack_trees(3, t, &opt->unpack_opts);\n+\trc = unpack_trees(3, t, &opt->priv->unpack_opts);\n \tcache_tree_free(&opt->repo->index->cache_tree);\n \n \t/*\n-\t * Update opt->repo->index to match the new results, AFTER saving a copy\n-\t * in opt->orig_index.  Update src_index to point to the saved copy.\n-\t * (verify_uptodate() checks src_index, and the original index is\n-\t * the one that had the necessary modification timestamps.)\n+\t * Update opt->repo->index to match the new results, AFTER saving a\n+\t * copy in opt->priv->orig_index.  Update src_index to point to the\n+\t * saved copy.  (verify_uptodate() checks src_index, and the original\n+\t * index is the one that had the necessary modification timestamps.)\n \t */\n-\topt->orig_index = *opt->repo->index;\n+\topt->priv->orig_index = *opt->repo->index;\n \t*opt->repo->index = tmp_index;\n-\topt->unpack_opts.src_index = &opt->orig_index;\n+\topt->priv->unpack_opts.src_index = &opt->priv->orig_index;\n \n \treturn rc;\n }\n \n static void unpack_trees_finish(struct merge_options *opt)\n {\n-\tdiscard_index(&opt->orig_index);\n-\tclear_unpack_trees_porcelain(&opt->unpack_opts);\n+\tdiscard_index(&opt->priv->orig_index);\n+\tclear_unpack_trees_porcelain(&opt->priv->unpack_opts);\n }\n \n static int save_files_dirs(const struct object_id *oid,\n@@ -454,7 +464,7 @@ static int save_files_dirs(const struct object_id *oid,\n \n \tFLEX_ALLOC_MEM(entry, path, base->buf, base->len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \n \tstrbuf_setlen(base, baselen);\n \treturn (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);\n@@ -585,7 +595,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t * If we're merging merge-bases, we don't want to bother with\n \t * any working directory changes.\n \t */\n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn;\n \n \t/* Ensure D/F conflicts are adjacent in the entries list. */\n@@ -597,7 +607,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \tdf_sorted_entries.cmp = string_list_df_name_compare;\n \tstring_list_sort(&df_sorted_entries);\n \n-\tstring_list_clear(&opt->df_conflict_file_set, 1);\n+\tstring_list_clear(&opt->priv->df_conflict_file_set, 1);\n \tfor (i = 0; i < df_sorted_entries.nr; i++) {\n \t\tconst char *path = df_sorted_entries.items[i].string;\n \t\tint len = strlen(path);\n@@ -613,7 +623,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t\t    len > last_len &&\n \t\t    memcmp(path, last_file, last_len) == 0 &&\n \t\t    path[last_len] == '/') {\n-\t\t\tstring_list_insert(&opt->df_conflict_file_set, last_file);\n+\t\t\tstring_list_insert(&opt->priv->df_conflict_file_set, last_file);\n \t\t}\n \n \t\t/*\n@@ -680,8 +690,8 @@ static void update_entry(struct stage_data *entry,\n static int remove_file(struct merge_options *opt, int clean,\n \t\t       const char *path, int no_wd)\n {\n-\tint update_cache = opt->call_depth || clean;\n-\tint update_working_directory = !opt->call_depth && !no_wd;\n+\tint update_cache = opt->priv->call_depth || clean;\n+\tint update_working_directory = !opt->priv->call_depth && !no_wd;\n \n \tif (update_cache) {\n \t\tif (remove_file_from_index(opt->repo->index, path))\n@@ -724,16 +734,16 @@ static char *unique_path(struct merge_options *opt,\n \tadd_flattened_path(&newpath, branch);\n \n \tbase_len = newpath.len;\n-\twhile (hashmap_get_from_hash(&opt->current_file_dir_set,\n+\twhile (hashmap_get_from_hash(&opt->priv->current_file_dir_set,\n \t\t\t\t     path_hash(newpath.buf), newpath.buf) ||\n-\t       (!opt->call_depth && file_exists(newpath.buf))) {\n+\t       (!opt->priv->call_depth && file_exists(newpath.buf))) {\n \t\tstrbuf_setlen(&newpath, base_len);\n \t\tstrbuf_addf(&newpath, \"_%d\", suffix++);\n \t}\n \n \tFLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \treturn strbuf_detach(&newpath, NULL);\n }\n \n@@ -775,7 +785,7 @@ static int dir_in_way(struct index_state *istate, const char *path,\n static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\t\t\t   const struct diff_filespec *blob)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \tstruct cache_entry *ce;\n \n \tif (0 > pos)\n@@ -783,7 +793,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\treturn 0;\n \n \t/* See if the file we were tracking before matches */\n-\tce = opt->orig_index.cache[pos];\n+\tce = opt->priv->orig_index.cache[pos];\n \treturn (oid_eq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);\n }\n \n@@ -792,7 +802,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n  */\n static int was_tracked(struct merge_options *opt, const char *path)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \n \tif (0 <= pos)\n \t\t/* we were tracking this path before the merge */\n@@ -849,12 +859,12 @@ static int was_dirty(struct merge_options *opt, const char *path)\n \tstruct cache_entry *ce;\n \tint dirty = 1;\n \n-\tif (opt->call_depth || !was_tracked(opt, path))\n+\tif (opt->priv->call_depth || !was_tracked(opt, path))\n \t\treturn !dirty;\n \n-\tce = index_file_exists(opt->unpack_opts.src_index,\n+\tce = index_file_exists(opt->priv->unpack_opts.src_index,\n \t\t\t       path, strlen(path), ignore_case);\n-\tdirty = verify_uptodate(ce, &opt->unpack_opts) != 0;\n+\tdirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;\n \treturn dirty;\n }\n \n@@ -864,8 +874,8 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \tconst char *msg = _(\"failed to create path '%s'%s\");\n \n \t/* Unlink any D/F conflict files that are in the way */\n-\tfor (i = 0; i < opt->df_conflict_file_set.nr; i++) {\n-\t\tconst char *df_path = opt->df_conflict_file_set.items[i].string;\n+\tfor (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {\n+\t\tconst char *df_path = opt->priv->df_conflict_file_set.items[i].string;\n \t\tsize_t pathlen = strlen(path);\n \t\tsize_t df_pathlen = strlen(df_path);\n \t\tif (df_pathlen < pathlen &&\n@@ -875,7 +885,7 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \t\t\t       _(\"Removing %s to make room for subdirectory\\n\"),\n \t\t\t       df_path);\n \t\t\tunlink(df_path);\n-\t\t\tunsorted_string_list_delete_item(&opt->df_conflict_file_set,\n+\t\t\tunsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,\n \t\t\t\t\t\t\t i, 0);\n \t\t\tbreak;\n \t\t}\n@@ -916,7 +926,7 @@ static int update_file_flags(struct merge_options *opt,\n {\n \tint ret = 0;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\tupdate_wd = 0;\n \n \tif (update_wd) {\n@@ -1001,7 +1011,7 @@ static int update_file(struct merge_options *opt,\n \t\t       const char *path)\n {\n \treturn update_file_flags(opt, contents, path,\n-\t\t\t\t opt->call_depth || clean, !opt->call_depth);\n+\t\t\t\t opt->priv->call_depth || clean, !opt->priv->call_depth);\n }\n \n /* Low level file merging, update and removal */\n@@ -1030,7 +1040,7 @@ static int merge_3way(struct merge_options *opt,\n \tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = opt->xdl_opts;\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\tll_opts.virtual_ancestor = 1;\n \t\tll_opts.variant = 0;\n \t} else {\n@@ -1161,7 +1171,7 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct object_array merges;\n \n \tint i;\n-\tint search = !opt->call_depth;\n+\tint search = !opt->priv->call_depth;\n \n \t/* store a in result in case we fail */\n \toidcpy(result, a);\n@@ -1383,7 +1393,7 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \t\t\t       MERGE_DIRECTORY_RENAMES_CONFLICT);\n \tassert(ren->dir_rename_original_dest);\n \n-\tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n+\tif (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) {\n \t\tmark_conflicted = 1;\n \t\tfile_path = unique_path(opt, dest->path, ren->branch);\n \t\toutput(opt, 1, _(\"Error: Refusing to lose untracked file at %s; \"\n@@ -1426,12 +1436,12 @@ static int handle_change_delete(struct merge_options *opt,\n \tconst char *update_path = path;\n \tint ret = 0;\n \n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0) ||\n-\t    (!opt->call_depth && would_lose_untracked(opt, path))) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) ||\n+\t    (!opt->priv->call_depth && would_lose_untracked(opt, path))) {\n \t\tupdate_path = alt_path = unique_path(opt, path, change_branch);\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * We cannot arbitrarily accept either a_sha or b_sha as\n \t\t * correct; since there is no true \"middle point\" between\n@@ -1506,14 +1516,14 @@ static int handle_rename_delete(struct merge_options *opt,\n \t\t\t\t     opt->branch2 : opt->branch1);\n \n \tif (handle_change_delete(opt,\n-\t\t\t\t opt->call_depth ? orig->path : dest->path,\n-\t\t\t\t opt->call_depth ? NULL : orig->path,\n+\t\t\t\t opt->priv->call_depth ? orig->path : dest->path,\n+\t\t\t\t opt->priv->call_depth ? NULL : orig->path,\n \t\t\t\t orig, dest,\n \t\t\t\t rename_branch, delete_branch,\n \t\t\t\t _(\"rename\"), _(\"renamed\")))\n \t\treturn -1;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn remove_file_from_index(opt->repo->index, dest->path);\n \telse\n \t\treturn update_stages(opt, dest->path, NULL,\n@@ -1550,7 +1560,7 @@ static int handle_file_collision(struct merge_options *opt,\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n-\tif (opt->call_depth && (prev_path1 || prev_path2)) {\n+\tif (opt->priv->call_depth && (prev_path1 || prev_path2)) {\n \t\t/* Put first file (a->oid, a->mode) in its original spot */\n \t\tif (prev_path1) {\n \t\t\tif (update_file(opt, 1, a, prev_path1))\n@@ -1579,10 +1589,10 @@ static int handle_file_collision(struct merge_options *opt,\n \t/* Remove rename sources if rename/add or rename/rename(2to1) */\n \tif (prev_path1)\n \t\tremove_file(opt, 1, prev_path1,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path1));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path1));\n \tif (prev_path2)\n \t\tremove_file(opt, 1, prev_path2,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path2));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path2));\n \n \t/*\n \t * Remove the collision path, if it wouldn't cause dirty contents\n@@ -1624,12 +1634,12 @@ static int handle_file_collision(struct merge_options *opt,\n \tnull.mode = 0;\n \n \tif (merge_mode_and_contents(opt, &null, a, b, collide_path,\n-\t\t\t\t    branch1, branch2, opt->call_depth * 2, &mfi))\n+\t\t\t\t    branch1, branch2, opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tmfi.clean &= !alt_path;\n \tif (update_file(opt, mfi.clean, &mfi.blob, update_path))\n \t\treturn -1;\n-\tif (!mfi.clean && !opt->call_depth &&\n+\tif (!mfi.clean && !opt->priv->call_depth &&\n \t    update_stages(opt, collide_path, NULL, a, b))\n \t\treturn -1;\n \tfree(alt_path);\n@@ -1669,7 +1679,7 @@ static int handle_rename_add(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[other_stage],\n \t\t\t\t    prev_path_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(prev_path_desc);\n \n@@ -1687,7 +1697,7 @@ static char *find_path_for_conflict(struct merge_options *opt,\n \t\t\t\t    const char *branch2)\n {\n \tchar *new_path = NULL;\n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0)) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) {\n \t\tnew_path = unique_path(opt, path, branch1);\n \t\toutput(opt, 1, _(\"%s is a directory in %s adding \"\n \t\t\t       \"as %s instead\"),\n@@ -1718,17 +1728,17 @@ static int handle_rename_rename_1to2(struct merge_options *opt,\n \t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\"),\n \t       o->path, a->path, ci->ren1->branch,\n \t       o->path, b->path, ci->ren2->branch,\n-\t       opt->call_depth ? _(\" (left unresolved)\") : \"\");\n+\t       opt->priv->call_depth ? _(\" (left unresolved)\") : \"\");\n \n \tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n \t\t\t    a->path, b->path, o->path);\n \tif (merge_mode_and_contents(opt, o, a, b, path_desc,\n \t\t\t\t    ci->ren1->branch, ci->ren2->branch,\n-\t\t\t\t    opt->call_depth * 2, &mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(path_desc);\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1843,12 +1853,12 @@ static int handle_rename_rename_2to1(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[ostage1],\n \t\t\t\t    path_side_1_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(opt, b,\n \t\t\t\t    &ci->ren2->src_entry->stages[ostage2],\n \t\t\t\t    c2, path_side_2_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -1889,8 +1899,8 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \tdiff_setup_done(&opts);\n \tdiff_tree_oid(&o_tree->object.oid, &tree->object.oid, \"\", &opts);\n \tdiffcore_std(&opts);\n-\tif (opts.needed_rename_limit > opt->needed_rename_limit)\n-\t\topt->needed_rename_limit = opts.needed_rename_limit;\n+\tif (opts.needed_rename_limit > opt->priv->needed_rename_limit)\n+\t\topt->priv->needed_rename_limit = opts.needed_rename_limit;\n \n \tret = xmalloc(sizeof(*ret));\n \t*ret = diff_queued_diff;\n@@ -2865,7 +2875,7 @@ static int detect_and_process_renames(struct merge_options *opt,\n \n \tif ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) ||\n \t    (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n-\t     !opt->call_depth)) {\n+\t     !opt->priv->call_depth)) {\n \t\tdir_re_head = get_directory_renames(head_pairs);\n \t\tdir_re_merge = get_directory_renames(merge_pairs);\n \n@@ -3022,13 +3032,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\treason = _(\"add/add\");\n \n \tassert(o->path && a->path && b->path);\n-\tif (ci && dir_in_way(opt->repo->index, path, !opt->call_depth,\n+\tif (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth,\n \t\t\t     S_ISGITLINK(ci->ren1->pair->two->mode)))\n \t\tdf_conflict_remains = 1;\n \n \tif (merge_mode_and_contents(opt, o, a, b, path,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    opt->call_depth * 2, mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, mfi))\n \t\treturn -1;\n \n \t/*\n@@ -3044,7 +3054,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \t\toutput(opt, 3, _(\"Skipped %s (merged same as existing)\"), path);\n \t\tif (add_cacheinfo(opt, &mfi->blob, path,\n-\t\t\t\t  0, (!opt->call_depth && !is_dirty), 0))\n+\t\t\t\t  0, (!opt->priv->call_depth && !is_dirty), 0))\n \t\t\treturn -1;\n \t\t/*\n \t\t * However, add_cacheinfo() will delete the old cache entry\n@@ -3052,8 +3062,8 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\t * flag to avoid making the file appear as if it were\n \t\t * deleted by the user.\n \t\t */\n-\t\tpos = index_name_pos(&opt->orig_index, path, strlen(path));\n-\t\tce = opt->orig_index.cache[pos];\n+\t\tpos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n+\t\tce = opt->priv->orig_index.cache[pos];\n \t\tif (ce_skip_worktree(ce)) {\n \t\t\tpos = index_name_pos(opt->repo->index, path, strlen(path));\n \t\t\tce = opt->repo->index->cache[pos];\n@@ -3074,7 +3084,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \tif (df_conflict_remains || is_dirty) {\n \t\tchar *new_path;\n-\t\tif (opt->call_depth) {\n+\t\tif (opt->priv->call_depth) {\n \t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t} else {\n \t\t\tif (!mfi->clean) {\n@@ -3333,7 +3343,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\tconf = _(\"directory/file\");\n \t\t}\n \t\tif (dir_in_way(opt->repo->index, path,\n-\t\t\t       !opt->call_depth && !S_ISGITLINK(a->mode),\n+\t\t\t       !opt->priv->call_depth && !S_ISGITLINK(a->mode),\n \t\t\t       0)) {\n \t\t\tchar *new_path = unique_path(opt, path, add_branch);\n \t\t\tclean_merge = 0;\n@@ -3342,7 +3352,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tif (update_file(opt, 0, contents, new_path))\n \t\t\t\tclean_merge = -1;\n-\t\t\telse if (opt->call_depth)\n+\t\t\telse if (opt->priv->call_depth)\n \t\t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t\tfree(new_path);\n \t\t} else {\n@@ -3407,7 +3417,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n-\t\tif (show(opt, 4) || opt->call_depth)\n+\t\tif (show(opt, 4) || opt->priv->call_depth)\n \t\t\terr(opt, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n@@ -3426,7 +3436,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\thashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp,\n \t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n@@ -3463,7 +3473,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free(&opt->current_file_dir_set, 1);\n+\t\thashmap_free(&opt->priv->current_file_dir_set, 1);\n \n \t\tif (clean < 0) {\n \t\t\tunpack_trees_finish(opt);\n@@ -3475,7 +3485,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth &&\n+\tif (opt->priv->call_depth &&\n \t    !(*result = write_inmemory_index_as_tree(opt->repo)))\n \t\treturn -1;\n \n@@ -3543,7 +3553,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \n \tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n-\t\topt->call_depth++;\n+\t\topt->priv->call_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n \t\t * with conflict markers. The cleanness flag is\n@@ -3562,14 +3572,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n-\t\topt->call_depth--;\n+\t\topt->priv->call_depth--;\n \n \t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n \tdiscard_index(opt->repo->index);\n-\tif (!opt->call_depth)\n+\tif (!opt->priv->call_depth)\n \t\trepo_read_index(opt->repo);\n \n \tswitch (num_merge_bases) {\n@@ -3597,7 +3607,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\treturn clean;\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t*result = make_virtual_commit(opt->repo, result_tree,\n \t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n@@ -3617,17 +3627,21 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \t\treturn -1;\n \t}\n \n+\topt->priv = xcalloc(1, sizeof(*opt->priv));\n+\tstring_list_init(&opt->priv->df_conflict_file_set, 1);\n \treturn 0;\n }\n \n static void merge_finalize(struct merge_options *opt)\n {\n \tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n+\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0);\n+\tfree(opt->priv);\n+\topt->priv = NULL;\n }\n \n int merge_trees(struct merge_options *opt,\n@@ -3772,8 +3786,6 @@ void init_merge_options(struct merge_options *opt,\n \n \topt->renormalize = 0;\n \n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n-\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 933d6e7642..58a4c5238a 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -1,13 +1,15 @@\n #ifndef MERGE_RECURSIVE_H\n #define MERGE_RECURSIVE_H\n \n-#include \"string-list.h\"\n-#include \"unpack-trees.h\"\n+#include \"strbuf.h\"\n \n struct commit;\n-\n+struct commit_list;\n+struct object_id;\n struct repository;\n+struct tree;\n \n+struct merge_options_internal;\n struct merge_options {\n \tstruct repository *repo;\n \n@@ -45,13 +47,8 @@ struct merge_options {\n \tconst char *subtree_shift;\n \tunsigned renormalize : 1;\n \n-\t/* internal fields used by the implementation (do NOT set these) */\n-\tint call_depth;\n-\tint needed_rename_limit;\n-\tstruct hashmap current_file_dir_set;\n-\tstruct string_list df_conflict_file_set;\n-\tstruct unpack_trees_options unpack_opts;\n-\tstruct index_state orig_index;\n+\t/* internal fields used by the implementation */\n+\tstruct merge_options_internal *priv;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380516","messageId":"20190815214053.16594-19-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 18/24] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:47Z","receivedAt":"2019-08-15T21:41:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We provided users with the ability to state whether they wanted rename\ndetection, and to put a limit on how much CPU would be spent.  Both of\nthese fields had multiple configuration parameters for setting them,\nwith one being a fallback and the other being an override.  However,\ninstead of implementing the logic for how to combine the multiple\nsource locations into the appropriate setting at config loading time,\nwe loaded and tracked both values and then made the code combine them\nevery time it wanted to check the overall value.  This had a few\nminor drawbacks:\n  * it seems more complicated than necessary\n  * it runs the risk of people using the independent settings in the\n    future and breaking the intent of how the options are used\n    together\n  * it makes merge_options more complicated than necessary for other\n    potential users of the API\n\nFix these problems by moving the logic for combining the pairs of\noptions into a single value; make it apply at time-of-config-loading\ninstead of each-time-of-use.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 27 +++++++++++----------------\n merge-recursive.h |  6 ++----\n 2 files changed, 13 insertions(+), 20 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e401114b8f..b846acf931 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -385,8 +385,7 @@ static int add_cacheinfo(struct merge_options *opt,\n \n static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+\treturn (opt->detect_renames != -1) ? opt->detect_renames : 1;\n }\n \n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n@@ -1883,9 +1882,7 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \t */\n \tif (opts.detect_rename > DIFF_DETECT_RENAME)\n \t\topts.detect_rename = DIFF_DETECT_RENAME;\n-\topts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n-\t\t\t    opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n-\t\t\t    1000;\n+\topts.rename_limit = (opt->rename_limit != -1) ? opt->rename_limit : 1000;\n \topts.rename_score = opt->rename_score;\n \topts.show_rename_progress = opt->show_rename_progress;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -3732,14 +3729,14 @@ static void merge_recursive_config(struct merge_options *opt)\n {\n \tchar *value = NULL;\n \tgit_config_get_int(\"merge.verbosity\", &opt->verbosity);\n-\tgit_config_get_int(\"diff.renamelimit\", &opt->diff_rename_limit);\n-\tgit_config_get_int(\"merge.renamelimit\", &opt->merge_rename_limit);\n+\tgit_config_get_int(\"diff.renamelimit\", &opt->rename_limit);\n+\tgit_config_get_int(\"merge.renamelimit\", &opt->rename_limit);\n \tif (!git_config_get_string(\"diff.renames\", &value)) {\n-\t\topt->diff_detect_rename = git_config_rename(\"diff.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"diff.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.renames\", &value)) {\n-\t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n@@ -3765,11 +3762,9 @@ void init_merge_options(struct merge_options *opt,\n \topt->repo = repo;\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->diff_rename_limit = -1;\n-\topt->merge_rename_limit = -1;\n+\topt->rename_limit = -1;\n \topt->renormalize = 0;\n-\topt->diff_detect_rename = -1;\n-\topt->merge_detect_rename = -1;\n+\topt->detect_renames = -1;\n \topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n@@ -3821,16 +3816,16 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \telse if (!strcmp(s, \"no-renormalize\"))\n \t\topt->renormalize = 0;\n \telse if (!strcmp(s, \"no-renames\"))\n-\t\topt->merge_detect_rename = 0;\n+\t\topt->detect_renames = 0;\n \telse if (!strcmp(s, \"find-renames\")) {\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t\topt->rename_score = 0;\n \t}\n \telse if (skip_prefix(s, \"find-renames=\", &arg) ||\n \t\t skip_prefix(s, \"rename-threshold=\", &arg)) {\n \t\tif ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)\n \t\t\treturn -1;\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t}\n \t/*\n \t * Please update $__git_merge_strategy_options in\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 0fdae904dd..f4bdfbc897 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -27,10 +27,8 @@ struct merge_options {\n \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n \t} detect_directory_renames;\n-\tint diff_detect_rename;\n-\tint merge_detect_rename;\n-\tint diff_rename_limit;\n-\tint merge_rename_limit;\n+\tint detect_renames;\n+\tint rename_limit;\n \tint rename_score;\n \tint needed_rename_limit;\n \tint show_rename_progress;\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380517","messageId":"20190815214053.16594-24-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:52Z","receivedAt":"2019-08-15T21:41:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are lots of options that callers can set, yet most have a limited\nrange of valid values, some options are meant for output (e.g.\nopt->obuf, which is expected to start empty), and callers are expected\nto not set opt->priv.  Add several sanity checks to ensure callers\nprovide sane values.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 23 +++++++++++++++++++++++\n merge-recursive.h |  2 +-\n 2 files changed, 24 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 647b1f25c3..bc0da608c4 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3620,6 +3620,29 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \n+\t/* Sanity checks on opt */\n+\tassert(opt->repo);\n+\n+\tassert(opt->branch1 && opt->branch2);\n+\n+\tassert(opt->detect_renames >= -1 &&\n+\t       opt->detect_renames <= DIFF_DETECT_COPY);\n+\tassert(opt->detect_directory_renames >= MERGE_DIRECTORY_RENAMES_NONE &&\n+\t       opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE);\n+\tassert(opt->rename_limit >= -1);\n+\tassert(opt->rename_score >= 0 && opt->rename_score <= MAX_SCORE);\n+\n+\tassert(opt->xdl_opts >= 0);\n+\tassert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&\n+\t       opt->recursive_variant <= MERGE_VARIANT_THEIRS);\n+\n+\tassert(opt->verbosity >= 0 && opt->verbosity <= 5);\n+\tassert(opt->buffer_output >= 0 && opt->buffer_output <= 2);\n+\tassert(opt->obuf.len == 0);\n+\n+\tassert(opt->priv == NULL);\n+\n+\t/* Sanity check on repo state; index must match head */\n \tif (repo_index_has_changes(opt->repo, head, &sb)) {\n \t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n \t\t    sb.buf);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 978847e672..d201ee80fb 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -27,7 +27,7 @@ struct merge_options {\n \t} detect_directory_renames;\n \tint rename_limit;\n \tint rename_score;\n-\tint show_rename_progress;\n+\tint show_rename_progress : 1;\n \n \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n \tlong xdl_opts;\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380518","messageId":"20190815214053.16594-23-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v3 22/24] merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-15T21:40:51Z","receivedAt":"2019-08-15T21:41:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"I want to implement the same outward facing API as found within\nmerge-recursive.h in a different merge strategy.  However, that makes\nnames like MERGE_RECURSIVE_{NORMAL,OURS,THEIRS} look a little funny;\nrename to MERGE_VARIANT_{NORMAL,OURS,THEIRS}.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 14 +++++++-------\n merge-recursive.h |  6 +++---\n 2 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 840b09f1dc..647b1f25c3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1045,10 +1045,10 @@ static int merge_3way(struct merge_options *opt,\n \t\tll_opts.variant = 0;\n \t} else {\n \t\tswitch (opt->recursive_variant) {\n-\t\tcase MERGE_RECURSIVE_OURS:\n+\t\tcase MERGE_VARIANT_OURS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_OURS;\n \t\t\tbreak;\n-\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n \t\t\tbreak;\n \t\tdefault:\n@@ -1355,15 +1355,15 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&b->oid);\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n-\t\t\tcase MERGE_RECURSIVE_NORMAL:\n+\t\t\tcase MERGE_VARIANT_NORMAL:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tif (!oid_eq(&a->oid, &b->oid))\n \t\t\t\t\tresult->clean = 0;\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_OURS:\n+\t\t\tcase MERGE_VARIANT_OURS:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\t\toidcpy(&result->blob.oid, &b->oid);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -3801,9 +3801,9 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \tif (!s || !*s)\n \t\treturn -1;\n \tif (!strcmp(s, \"ours\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_OURS;\n+\t\topt->recursive_variant = MERGE_VARIANT_OURS;\n \telse if (!strcmp(s, \"theirs\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_THEIRS;\n+\t\topt->recursive_variant = MERGE_VARIANT_THEIRS;\n \telse if (!strcmp(s, \"subtree\"))\n \t\topt->subtree_shift = \"\";\n \telse if (skip_prefix(s, \"subtree=\", &arg))\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 58a4c5238a..978847e672 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -32,9 +32,9 @@ struct merge_options {\n \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n \tlong xdl_opts;\n \tenum {\n-\t\tMERGE_RECURSIVE_NORMAL = 0,\n-\t\tMERGE_RECURSIVE_OURS,\n-\t\tMERGE_RECURSIVE_THEIRS\n+\t\tMERGE_VARIANT_NORMAL = 0,\n+\t\tMERGE_VARIANT_OURS,\n+\t\tMERGE_VARIANT_THEIRS\n \t} recursive_variant;\n \n \t/* console output related options */\n-- \n2.23.0.rc2.32.g2123e9e4e4\n\n"},{"id":"380547","messageId":"xmqqd0h428ig.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-24-newren@gmail.com","subject":"Re: [PATCH v3 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T19:52:07Z","receivedAt":"2019-08-16T19:52:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> There are lots of options that callers can set, yet most have a limited\n> range of valid values, some options are meant for output (e.g.\n> opt->obuf, which is expected to start empty), and callers are expected\n> to not set opt->priv.  Add several sanity checks to ensure callers\n> provide sane values.\n> ...\n\nThe change to the struct does not seem to have much with the above\nrationale.\n\n> diff --git a/merge-recursive.h b/merge-recursive.h\n> index 978847e672..d201ee80fb 100644\n> --- a/merge-recursive.h\n> +++ b/merge-recursive.h\n> @@ -27,7 +27,7 @@ struct merge_options {\n>  \t} detect_directory_renames;\n>  \tint rename_limit;\n>  \tint rename_score;\n> -\tint show_rename_progress;\n> +\tint show_rename_progress : 1;\n\nAnd a one-bit wide bitfield that is signed is always an iffy idea.\n\n>  \n>  \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n>  \tlong xdl_opts;\n"},{"id":"380548","messageId":"xmqq8srs286r.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-24-newren@gmail.com","subject":"Re: [PATCH v3 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T19:59:08Z","receivedAt":"2019-08-16T19:59:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 647b1f25c3..bc0da608c4 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3620,6 +3620,29 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n>  ...\n> +\tassert(opt->buffer_output >= 0 && opt->buffer_output <= 2);\n\nThe field is unsigned, so >=0 side triggers \"-Werror=type-limits\" warning.\n\nMaterial for squashing I have collected so far...\n\n cache-tree.c      | 2 +-\n merge-recursive.c | 2 +-\n 2 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 00eda3e537..ef8c9f5e04 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -608,7 +608,7 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat\n \treturn it;\n }\n \n-int write_index_as_tree_internal(struct object_id *oid, struct index_state *index_state, int cache_tree_valid, int flags, const char *prefix)\n+static int write_index_as_tree_internal(struct object_id *oid, struct index_state *index_state, int cache_tree_valid, int flags, const char *prefix)\n {\n \tif (flags & WRITE_TREE_IGNORE_CACHE_TREE) {\n \t\tcache_tree_free(&index_state->cache_tree);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d3dc3d8a49..3d126dcc48 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3638,7 +3638,7 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \t       opt->recursive_variant <= MERGE_VARIANT_THEIRS);\n \n \tassert(opt->verbosity >= 0 && opt->verbosity <= 5);\n-\tassert(opt->buffer_output >= 0 && opt->buffer_output <= 2);\n+\tassert(opt->buffer_output <= 2);\n \tassert(opt->obuf.len == 0);\n \n \tassert(opt->priv == NULL);\n"},{"id":"380553","messageId":"xmqqtvagzurk.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-4-newren@gmail.com","subject":"Re: [PATCH v3 03/24] merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T21:05:03Z","receivedAt":"2019-08-16T21:05:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> We always want our conflict hunks to be labelled so that users can know\n> where each came from.  The previous commit fixed the one caller in the\n> codebase which was not setting opt->ancestor (and thus not providing a\n> label for the \"merge base\" conflict hunk in diff3-style conflict\n> markers); add an assertion to prevent future codepaths from also\n> overlooking this requirement.\n\nMakes sense.\n"},{"id":"380554","messageId":"20190816211922.GS20404@szeder.dev","threadId":"51526","inReplyTo":"20190815214053.16594-22-newren@gmail.com","subject":"Re: [PATCH v3 21/24] merge-recursive: split internal fields into a separate struct","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-08-16T21:19:22Z","receivedAt":"2019-08-16T21:19:29Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Thu, Aug 15, 2019 at 02:40:50PM -0700, Elijah Newren wrote:\n> diff --git a/merge-recursive.c b/merge-recursive.c\n\n>  static void merge_finalize(struct merge_options *opt)\n>  {\n>  \tflush_output(opt);\n> -\tif (!opt->call_depth && opt->buffer_output < 2)\n> +\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n>  \t\tstrbuf_release(&opt->obuf);\n>  \tif (show(opt, 2))\n>  \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n> -\t\t\t\t       opt->needed_rename_limit, 0);\n> +\t\t\t\t       opt->priv->needed_rename_limit, 0);\n> +\tfree(opt->priv);\n> +\topt->priv = NULL;\n\nCoccinelle suggests FREE_AND_NULL(opt->priv) here.\n\n"},{"id":"380556","messageId":"xmqqpnl4ztfl.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-5-newren@gmail.com","subject":"Re: [PATCH v3 04/24] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T21:33:50Z","receivedAt":"2019-08-16T21:34:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> @@ -3507,6 +3507,11 @@ int merge_recursive(struct merge_options *opt,\n>  \tstruct commit *merged_common_ancestors;\n>  \tstruct tree *mrtree;\n>  \tint clean;\n> +\tint num_merge_bases;\n> +\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n> +\n> +\tif (!opt->call_depth)\n> +\t\tassert(opt->ancestor == NULL);\n\nHmph.  Do we have anything to say on this field when call_depth is\nnot zero?  Is it OK for opt->ancestor to be sometimes NULL and non\nNULL some other times?\n\n> @@ -3528,6 +3533,7 @@ int merge_recursive(struct merge_options *opt,\n>  \t\t\toutput_commit_title(opt, iter->item);\n>  \t}\n>  \n> +\tnum_merge_bases = commit_list_count(ca);\n\nCriss-cross merge with very large number of merge bases is rare, so\nit is OK to count them all, even though we only care about \"is it\nzero, is it one, or is it two or more?\"\n\nI suspect this does not have to count, though, if we really wanted\nto avoid counting.\n\n>  \tmerged_common_ancestors = pop_commit(&ca);\n>  \tif (merged_common_ancestors == NULL) {\n>  \t\t/* if there is no common ancestor, use an empty tree */\n\nHere is the case where we can already decide the ancestor name for\nthe later merge_trees() should be \"empty tree\".\n\nAnd if merged_common_ancestors is not NULL, ca may have run out (in\nwhich case, we only have a single merge base), or ca still has\nanother merge base (in which case, we have two or more).  So, if you\nadd\n\t\tancestor_name = \"empty tree\";\n\t} else if (ca) {\n\t\tancestor_name = \"merged common ancestors\";\n\t} else {\n\t\tancestor_name = abbrev_name(merged_common_ancestors);\n\t}\n\nto that if() statement above, that should be sufficient, no?\n\nopt is used for inner merge in the for() loop, so you would probably\nneed another \"char *\" variable without contaminating opt->ancestor_name\nat this point, and then assign the value in the temporary to the\nopt->ancestor field where the original always assigned \"merged\ncommon ancestors\".\n\n> @@ -3568,10 +3574,23 @@ int merge_recursive(struct merge_options *opt,\n>  \tif (!opt->call_depth)\n>  \t\trepo_read_index(opt->repo);\n>  \n> -\topt->ancestor = \"merged common ancestors\";\n> +\tswitch (num_merge_bases) {\n> +\tcase 0:\n> +\t\topt->ancestor = \"<empty tree>\";\n\nAlso, I do not see a reason why you want angle bra-ket pair around\n\"empty tree\".  You are already using \"merged common ancestors\"\nliteral phrase without any special marker syntax.\n\nThanks.\n"},{"id":"380557","messageId":"xmqqlfvszs5n.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-13-newren@gmail.com","subject":"Re: [PATCH v3 12/24] cache-tree: share code between functions writing an index as a tree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T22:01:24Z","receivedAt":"2019-08-16T22:01:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> write_tree_from_memory() appeared to be a merge-recursive special that\n> basically duplicated write_index_as_tree().  The two have a different\n> signature, but the bigger difference was just that write_index_as_tree()\n> would always unconditionally read the index off of disk instead of\n> working on the current in-memory index.  So:\n>\n>   * split out common code into write_index_as_tree_internal()\n>\n>   * rename write_tree_from_memory() to write_inmemory_index_as_tree(),\n\nSomewhat minor, but I find \"inmemory_index\" hard to see while\nscanning the patch.  Perhaps call it \"in_core_index\" instead?\n\nI originally started the above with \"Very minor, ...\", but as this\nis exposed to the public in a header file, the name matters a bit\nmore than that.\n"},{"id":"380558","messageId":"CABPp-BFE+EX9HtAqN7CeBNHDTObGVw6nT8=+xxAASPJPXAq2=g@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqd0h428ig.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-16T22:08:40Z","receivedAt":"2019-08-16T22:08:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 16, 2019 at 12:52 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > There are lots of options that callers can set, yet most have a limited\n> > range of valid values, some options are meant for output (e.g.\n> > opt->obuf, which is expected to start empty), and callers are expected\n> > to not set opt->priv.  Add several sanity checks to ensure callers\n> > provide sane values.\n> > ...\n>\n> The change to the struct does not seem to have much with the above\n> rationale.\n\nIf the only possible values are 0 and 1, I can either add assertions\nto check that at run time, or make the compiler check it for us by\nconfining its value to a single bit.  A compile-time check seems more\nrobust...\n\n> > diff --git a/merge-recursive.h b/merge-recursive.h\n> > index 978847e672..d201ee80fb 100644\n> > --- a/merge-recursive.h\n> > +++ b/merge-recursive.h\n> > @@ -27,7 +27,7 @@ struct merge_options {\n> >       } detect_directory_renames;\n> >       int rename_limit;\n> >       int rename_score;\n> > -     int show_rename_progress;\n> > +     int show_rename_progress : 1;\n>\n> And a one-bit wide bitfield that is signed is always an iffy idea.\n\n...assuming of course I don't mess it up like this, not noticing that\nit's an int that needs to be changed to an unsigned.  I'll fix this\nup.\n\nBut the fact that you flagged the struct change -- would you prefer\nsome commit message explanation of how it's related, or was it more\nthe case that you felt it was a different kind of change and wanted it\nsplit out into a separate patch?  I'm suspecting the former but am not\nquite sure.\n"},{"id":"380559","messageId":"CABPp-BGb32Vc+TC5f-30Rh6-1QCan4DP84_XkX6YLD63So-s4Q@mail.gmail.com","threadId":"51526","inReplyTo":"xmqq8srs286r.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-16T22:09:20Z","receivedAt":"2019-08-16T22:09:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 16, 2019 at 12:59 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> > diff --git a/merge-recursive.c b/merge-recursive.c\n> > index 647b1f25c3..bc0da608c4 100644\n> > --- a/merge-recursive.c\n> > +++ b/merge-recursive.c\n> > @@ -3620,6 +3620,29 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n> >  ...\n> > +     assert(opt->buffer_output >= 0 && opt->buffer_output <= 2);\n>\n> The field is unsigned, so >=0 side triggers \"-Werror=type-limits\" warning.\n>\n> Material for squashing I have collected so far...\n<snip>\n\nThanks, will include all of this in my next round.\n"},{"id":"380560","messageId":"xmqqftm0zrjs.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-19-newren@gmail.com","subject":"Re: [PATCH v3 18/24] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T22:14:31Z","receivedAt":"2019-08-16T22:14:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>  static inline int merge_detect_rename(struct merge_options *opt)\n>  {\n> -\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n> -\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n> +\treturn (opt->detect_renames != -1) ? opt->detect_renames : 1;\n>  }\n\nEvery time I see \"is it not negative?\" (or more generally \"is it in\nthis range?\") converted to \"is it not this exact value?\", it makes\nme feel uneasy.\n\n> -\topts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n> -\t\t\t    opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n> -\t\t\t    1000;\n> +\topts.rename_limit = (opt->rename_limit != -1) ? opt->rename_limit : 1000;\n\nLikewise.  I have no objection to merging two rename-limit to a\nsingle field (and two detect-renames to a single field).\n\n> @@ -3732,14 +3729,14 @@ static void merge_recursive_config(struct merge_options *opt)\n>  {\n>  \tchar *value = NULL;\n>  \tgit_config_get_int(\"merge.verbosity\", &opt->verbosity);\n> -\tgit_config_get_int(\"diff.renamelimit\", &opt->diff_rename_limit);\n> -\tgit_config_get_int(\"merge.renamelimit\", &opt->merge_rename_limit);\n> +\tgit_config_get_int(\"diff.renamelimit\", &opt->rename_limit);\n> +\tgit_config_get_int(\"merge.renamelimit\", &opt->rename_limit);\n\nHmph.  If merge.renameLimit is there, that would overwrite whatever\nwe get by reading from diff.renameLimit, so the two fields with\nruntime precedence order can easily be replaced by these two calls.\n\nNice.\n\nIf you have \"[diff] renamelimit = -2\" in your $GIT_DIR/config, would\nwe change behaviour due to the earlier conversion that has nothing\nto do with the theme of this step (i.e. consolidate two variables\ninto one)?\n\n> @@ -3765,11 +3762,9 @@ void init_merge_options(struct merge_options *opt,\n>  \topt->repo = repo;\n>  \topt->verbosity = 2;\n>  \topt->buffer_output = 1;\n> -\topt->diff_rename_limit = -1;\n> -\topt->merge_rename_limit = -1;\n> +\topt->rename_limit = -1;\n>  \topt->renormalize = 0;\n> -\topt->diff_detect_rename = -1;\n> -\topt->merge_detect_rename = -1;\n> +\topt->detect_renames = -1;\n>  \topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n>  \tmerge_recursive_config(opt);\n>  \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n> @@ -3821,16 +3816,16 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n>  \telse if (!strcmp(s, \"no-renormalize\"))\n>  \t\topt->renormalize = 0;\n>  \telse if (!strcmp(s, \"no-renames\"))\n> -\t\topt->merge_detect_rename = 0;\n> +\t\topt->detect_renames = 0;\n>  \telse if (!strcmp(s, \"find-renames\")) {\n> -\t\topt->merge_detect_rename = 1;\n> +\t\topt->detect_renames = 1;\n>  \t\topt->rename_score = 0;\n>  \t}\n>  \telse if (skip_prefix(s, \"find-renames=\", &arg) ||\n>  \t\t skip_prefix(s, \"rename-threshold=\", &arg)) {\n>  \t\tif ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)\n>  \t\t\treturn -1;\n> -\t\topt->merge_detect_rename = 1;\n> +\t\topt->detect_renames = 1;\n>  \t}\n>  \t/*\n>  \t * Please update $__git_merge_strategy_options in\n> diff --git a/merge-recursive.h b/merge-recursive.h\n> index 0fdae904dd..f4bdfbc897 100644\n> --- a/merge-recursive.h\n> +++ b/merge-recursive.h\n> @@ -27,10 +27,8 @@ struct merge_options {\n>  \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n>  \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n>  \t} detect_directory_renames;\n> -\tint diff_detect_rename;\n> -\tint merge_detect_rename;\n> -\tint diff_rename_limit;\n> -\tint merge_rename_limit;\n> +\tint detect_renames;\n> +\tint rename_limit;\n>  \tint rename_score;\n>  \tint needed_rename_limit;\n>  \tint show_rename_progress;\n"},{"id":"380561","messageId":"xmqqblwozrf3.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190815214053.16594-22-newren@gmail.com","subject":"Re: [PATCH v3 21/24] merge-recursive: split internal fields into a separate struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T22:17:20Z","receivedAt":"2019-08-16T22:17:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> merge_options has several internal fields that should not be set or read\n> by external callers.  This just complicates the API.  Move them into an\n> opaque merge_options_internal struct that is defined only in\n> merge-recursive.c and keep these out of merge-recursive.h.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 186 ++++++++++++++++++++++++----------------------\n>  merge-recursive.h |  17 ++---\n>  2 files changed, 106 insertions(+), 97 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index b4334d0506..840b09f1dc 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3617,17 +3627,21 @@ static int merge_start(struct merge_options *opt, struct tree *he> ...\n> -\t\t\t\t       opt->needed_rename_limit, 0);\n> +\t\t\t\t       opt->priv->needed_rename_limit, 0);\n> +\tfree(opt->priv);\n> +\topt->priv = NULL;\n\nThis gets hit by Cocci.\n"},{"id":"380563","messageId":"CABPp-BHuJB6RZ0htNa8Vmah++MiQFTvcGSDpdhtQ1cZBFwZSNQ@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqpnl4ztfl.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 04/24] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-16T22:39:00Z","receivedAt":"2019-08-16T22:39:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 16, 2019 at 2:33 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > @@ -3507,6 +3507,11 @@ int merge_recursive(struct merge_options *opt,\n> >       struct commit *merged_common_ancestors;\n> >       struct tree *mrtree;\n> >       int clean;\n> > +     int num_merge_bases;\n> > +     struct strbuf merge_base_abbrev = STRBUF_INIT;\n> > +\n> > +     if (!opt->call_depth)\n> > +             assert(opt->ancestor == NULL);\n>\n> Hmph.  Do we have anything to say on this field when call_depth is\n> not zero?  Is it OK for opt->ancestor to be sometimes NULL and non\n> NULL some other times?\n\nI was specifically trying to add a check for external callers of\nmerge_recursive() to make sure they called it correctly.  Since\nmerge_recursive() sets opt->ancestor before calling itself\nrecursively, I had to hide the assertion behind an if-check, namely on\ncall_depth.\n\nWe could add an assertion that opt->ancestor != NULL when\nopt->call_depth > 0, but it seemed odd to document pre-conditions for\nhow merge_recursive() calls itself.  Anyway, this code block actually\nbecomes a bit cleaner later in the series when I create  separate\nmerge_recursive() and merge_recursive_internal() functions, as the\nassertion can just go into merge_recursive() and not be protected by\nthe opt->call_depth check.\n\n> > @@ -3528,6 +3533,7 @@ int merge_recursive(struct merge_options *opt,\n> >                       output_commit_title(opt, iter->item);\n> >       }\n> >\n> > +     num_merge_bases = commit_list_count(ca);\n>\n> Criss-cross merge with very large number of merge bases is rare, so\n> it is OK to count them all, even though we only care about \"is it\n> zero, is it one, or is it two or more?\"\n>\n> I suspect this does not have to count, though, if we really wanted\n> to avoid counting.\n>\n> >       merged_common_ancestors = pop_commit(&ca);\n> >       if (merged_common_ancestors == NULL) {\n> >               /* if there is no common ancestor, use an empty tree */\n>\n> Here is the case where we can already decide the ancestor name for\n> the later merge_trees() should be \"empty tree\".\n>\n> And if merged_common_ancestors is not NULL, ca may have run out (in\n> which case, we only have a single merge base), or ca still has\n> another merge base (in which case, we have two or more).  So, if you\n> add\n>                 ancestor_name = \"empty tree\";\n>         } else if (ca) {\n>                 ancestor_name = \"merged common ancestors\";\n>         } else {\n>                 ancestor_name = abbrev_name(merged_common_ancestors);\n>         }\n>\n> to that if() statement above, that should be sufficient, no?\n>\n> opt is used for inner merge in the for() loop, so you would probably\n> need another \"char *\" variable without contaminating opt->ancestor_name\n> at this point, and then assign the value in the temporary to the\n> opt->ancestor field where the original always assigned \"merged\n> common ancestors\".\n\nSure, I can make these changes.\n\n> > @@ -3568,10 +3574,23 @@ int merge_recursive(struct merge_options *opt,\n> >       if (!opt->call_depth)\n> >               repo_read_index(opt->repo);\n> >\n> > -     opt->ancestor = \"merged common ancestors\";\n> > +     switch (num_merge_bases) {\n> > +     case 0:\n> > +             opt->ancestor = \"<empty tree>\";\n>\n> Also, I do not see a reason why you want angle bra-ket pair around\n> \"empty tree\".  You are already using \"merged common ancestors\"\n> literal phrase without any special marker syntax.\n\nOh good point; will drop.\n"},{"id":"380564","messageId":"CABPp-BENt6ejUNOxorXPXywb72hsD+1Hs1Gja6ZsfrUsMrTkpA@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqlfvszs5n.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 12/24] cache-tree: share code between functions writing an index as a tree","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-16T22:39:40Z","receivedAt":"2019-08-16T22:39:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 16, 2019 at 3:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > write_tree_from_memory() appeared to be a merge-recursive special that\n> > basically duplicated write_index_as_tree().  The two have a different\n> > signature, but the bigger difference was just that write_index_as_tree()\n> > would always unconditionally read the index off of disk instead of\n> > working on the current in-memory index.  So:\n> >\n> >   * split out common code into write_index_as_tree_internal()\n> >\n> >   * rename write_tree_from_memory() to write_inmemory_index_as_tree(),\n>\n> Somewhat minor, but I find \"inmemory_index\" hard to see while\n> scanning the patch.  Perhaps call it \"in_core_index\" instead?\n>\n> I originally started the above with \"Very minor, ...\", but as this\n> is exposed to the public in a header file, the name matters a bit\n> more than that.\n\nSounds good; will change.\n"},{"id":"380565","messageId":"CABPp-BF6LPszUcKEwFGPr2DuFRZGU00uM-ky5YhKWw+het2QRA@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqftm0zrjs.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 18/24] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-16T22:59:12Z","receivedAt":"2019-08-16T22:59:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 16, 2019 at 3:14 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> >  static inline int merge_detect_rename(struct merge_options *opt)\n> >  {\n> > -     return opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n> > -             opt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n> > +     return (opt->detect_renames != -1) ? opt->detect_renames : 1;\n> >  }\n>\n> Every time I see \"is it not negative?\" (or more generally \"is it in\n> this range?\") converted to \"is it not this exact value?\", it makes\n> me feel uneasy.\n>\n> > -     opts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n> > -                         opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n> > -                         1000;\n> > +     opts.rename_limit = (opt->rename_limit != -1) ? opt->rename_limit : 1000;\n>\n> Likewise.  I have no objection to merging two rename-limit to a\n> single field (and two detect-renames to a single field).\n>\n> > @@ -3732,14 +3729,14 @@ static void merge_recursive_config(struct merge_options *opt)\n> >  {\n> >       char *value = NULL;\n> >       git_config_get_int(\"merge.verbosity\", &opt->verbosity);\n> > -     git_config_get_int(\"diff.renamelimit\", &opt->diff_rename_limit);\n> > -     git_config_get_int(\"merge.renamelimit\", &opt->merge_rename_limit);\n> > +     git_config_get_int(\"diff.renamelimit\", &opt->rename_limit);\n> > +     git_config_get_int(\"merge.renamelimit\", &opt->rename_limit);\n>\n> Hmph.  If merge.renameLimit is there, that would overwrite whatever\n> we get by reading from diff.renameLimit, so the two fields with\n> runtime precedence order can easily be replaced by these two calls.\n>\n> Nice.\n>\n> If you have \"[diff] renamelimit = -2\" in your $GIT_DIR/config, would\n> we change behaviour due to the earlier conversion that has nothing\n> to do with the theme of this step (i.e. consolidate two variables\n> into one)?\n\nAt the end of this series, the \"merge-recursive: add sanity checks for\nrelevant merge_options\" commit adds some assertions that would fail if\nsomeone passed such a value, regardless of whether this patch was\nincluded or not.  (Are we worried about people having such a config\nvalue and should we support it?  It goes against the documented\nvalues, but I guess someone could have set it anyway even if it seems\nodd to set a value that says, \"give me whatever the default is.\")\n\nIf we tried with this specific commit, though, then: diffcore-rename.c\nchecks for rename_limit <= 0 and sets the value to 32767 in that case,\nso it'd have the effect of extending the default merge-related rename\nlimit.  As far as detect_rename, diff.c treats that as a boolean in\nmost places (e.g. \"if (options->detect_rename)\"), and specifically\ncompares against DIFF_DETECT_COPY in places where copy detection are\nrelevant.  So, detect_rename would behave the same.\n\nAll of that said, I'm happy to restore the is-not-negative checks.\n"},{"id":"380566","messageId":"CABPp-BHcrfMYOxJXAke5R3nq2hqJVupmOCNv5NnWoLT3W=Odcg@mail.gmail.com","threadId":"51526","inReplyTo":"20190816211922.GS20404@szeder.dev","subject":"Re: [PATCH v3 21/24] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-16T23:00:45Z","receivedAt":"2019-08-16T23:00:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 16, 2019 at 2:19 PM SZEDER Gábor <szeder.dev@gmail.com> wrote:\n>\n> On Thu, Aug 15, 2019 at 02:40:50PM -0700, Elijah Newren wrote:\n> > diff --git a/merge-recursive.c b/merge-recursive.c\n>\n> >  static void merge_finalize(struct merge_options *opt)\n> >  {\n> >       flush_output(opt);\n> > -     if (!opt->call_depth && opt->buffer_output < 2)\n> > +     if (!opt->priv->call_depth && opt->buffer_output < 2)\n> >               strbuf_release(&opt->obuf);\n> >       if (show(opt, 2))\n> >               diff_warn_rename_limit(\"merge.renamelimit\",\n> > -                                    opt->needed_rename_limit, 0);\n> > +                                    opt->priv->needed_rename_limit, 0);\n> > +     free(opt->priv);\n> > +     opt->priv = NULL;\n>\n> Coccinelle suggests FREE_AND_NULL(opt->priv) here.\n\nThanks; will fix.\n"},{"id":"380568","messageId":"xmqq7e7czope.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"CABPp-BFE+EX9HtAqN7CeBNHDTObGVw6nT8=+xxAASPJPXAq2=g@mail.gmail.com","subject":"Re: [PATCH v3 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T23:15:57Z","receivedAt":"2019-08-16T23:16:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> If the only possible values are 0 and 1, I can either add assertions\n> to check that at run time, or make the compiler check it for us by\n> confining its value to a single bit.  A compile-time check seems more\n> robust...\n\nSure, as long as they can catch assignments (e.g. \".field = 2\",\nor more interestingly \".field = .field + 1\" in a loop, etc.) and\nincrements or decrements and flag them.\n\n> But the fact that you flagged the struct change -- would you prefer\n> some commit message explanation of how it's related, or was it more\n> the case that you felt it was a different kind of change and wanted it\n> split out into a separate patch?  I'm suspecting the former but am not\n> quite sure.\n\nI do not see it as related at all, so either split it out into a\nseparate patch, or just drop it (and have a runtime check as\neverybody else in this step), would be the sensible alternatives.  I\nthink the latter is easier to reason about but it may be just me.\n"},{"id":"380569","messageId":"xmqq36i0zob3.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"CABPp-BF6LPszUcKEwFGPr2DuFRZGU00uM-ky5YhKWw+het2QRA@mail.gmail.com","subject":"Re: [PATCH v3 18/24] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-16T23:24:32Z","receivedAt":"2019-08-16T23:24:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> At the end of this series, the \"merge-recursive: add sanity checks for\n> relevant merge_options\" commit adds some assertions that would fail if\n> someone passed such a value, regardless of whether this patch was\n> included or not.  (Are we worried about people having such a config\n> value and should we support it?  It goes against the documented\n> values, but I guess someone could have set it anyway even if it seems\n> odd to set a value that says, \"give me whatever the default is.\")\n\nI am somewhat worried about changing the rule on the users without\ntelling them (and without having a good reason to enforce a tighter\nversion retroactively).\n\nIf we are formally forbidding \"[diff] renameLimit = -1\" and other\nout-of-bound values, (1) assert() will most often turn into noop in\nnon-debug builds, so the last step of this series may not be such a\ngood sanity check anyway, (2) \"if (condition) BUG();\" may be better,\nbut that's for catching programming errors that made the condition\nhold and configuration the end-user had should not trigger it.\n\nSo if we want to really catch bogus end-user-supplied values (and we\nreally do---but the definition of \"bogus\" may be debatable), the\nplace to do so is not the \"sanity-check at the end\", but where we\ncall get_config_int() and friends.\n\n"},{"id":"380586","messageId":"20190817184144.32179-2-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 01/24] merge-recursive: be consistent with assert","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:21Z","receivedAt":"2019-08-17T18:42:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 8daec1df03de (\"merge-recursive: switch from (oid,mode) pairs\nto a diff_filespec\", 2019-04-05), an assertion on a->path && b->path\nwas added for code readability to document that these both needed to be\nnon-NULL at this point in the code.  However, the subsequent lines also\nread o->path, so it should be included in the assert.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 6b812d67e3..1d960fa64b 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1043,7 +1043,7 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path);\n+\tassert(a->path && b->path && o->path);\n \tif (strcmp(a->path, b->path) ||\n \t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n \t\tbase_name = opt->ancestor == NULL ? NULL :\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380587","messageId":"20190817184144.32179-1-newren@gmail.com","threadId":"51526","inReplyTo":"20190815214053.16594-1-newren@gmail.com","subject":"[PATCH v4 00/24] Clean up merge API","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:20Z","receivedAt":"2019-08-17T18:42:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Before writing a replacement merge strategy for recursive, I decided to\nfirst cleanup the merge API -- streamlining merge-recursive.h and making\nit more readable.  This includes some minor fixes found along the way.\n\nChanges since v3 (full range-diff below):\n  * Addressed feedback from Junio and SZEDER:\n    * [Patch 4] Avoided looping over commit_list in determining\n                ancestor_name\n    * [Patch 12] s/inmemory/in_core/ in new cache-tree function name\n    * [Patch 18] restored the '>= 0' comparision\n    * [Patch 21] Use FREE_AND_NULL\n    * [Patch 23] Use range checks for show_rename_progress (don't use\n                 a bitfield), and don't compare an unsigned using >= 0.\n\nNotes:\n  * Patches 7, 9, 13, 14, and 15 only changed in the range-diff due to\n    context changes from other patches\n\nDerrick Stolee (1):\n  merge-recursive: introduce an enum for detect_directory_renames values\n\nElijah Newren (23):\n  merge-recursive: be consistent with assert\n  checkout: provide better conflict hunk description with detached HEAD\n  merge-recursive: enforce opt->ancestor != NULL when calling\n    merge_trees()\n  merge-recursive: provide a better label for diff3 common ancestor\n  merge-recursive: future-proof update_file_flags() against memory leaks\n  merge-recursive: remove another implicit dependency on the_repository\n  Ensure index matches head before invoking merge machinery, round N\n  merge-recursive: exit early if index != head\n  merge-recursive: remove useless parameter in merge_trees()\n  merge-recursive: don't force external callers to do our logging\n  cache-tree: share code between functions writing an index as a tree\n  merge-recursive: fix some overly long lines\n  merge-recursive: use common name for ancestors/common/base_list\n  merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n  merge-recursive: rename merge_options argument to opt in header\n  merge-recursive: move some definitions around to clean up the header\n  merge-recursive: consolidate unnecessary fields in merge_options\n  merge-recursive: comment and reorder the merge_options fields\n  merge-recursive: avoid losing output and leaking memory holding that\n    output\n  merge-recursive: split internal fields into a separate struct\n  merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n  merge-recursive: add sanity checks for relevant merge_options\n  merge-recursive: alphabetize include list\n\n builtin/am.c                      |   2 +-\n builtin/checkout.c                |  14 +-\n builtin/merge-recursive.c         |   4 +\n builtin/stash.c                   |   2 +\n cache-tree.c                      |  85 +++--\n cache-tree.h                      |   3 +-\n merge-recursive.c                 | 566 ++++++++++++++++++------------\n merge-recursive.h                 | 164 +++++----\n sequencer.c                       |   5 +-\n t/t3030-merge-recursive.sh        |   9 +-\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 189 ++++++++++\n 12 files changed, 717 insertions(+), 334 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\nRange-diff:\n 1:  21f1e04dc9 =  1:  7f99c2401e merge-recursive: be consistent with assert\n 2:  ac24702773 =  2:  b305348f45 checkout: provide better conflict hunk description with detached HEAD\n 3:  fd14ed9490 =  3:  5b9e32a356 merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()\n 4:  540a1d17d7 !  4:  566a55cf76 merge-recursive: provide a better label for diff3 common ancestor\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \tstruct commit *merged_common_ancestors;\n      \tstruct tree *mrtree;\n      \tint clean;\n    -+\tint num_merge_bases;\n    ++\tconst char *ancestor_name;\n     +\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n     +\n     +\tif (!opt->call_depth)\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \tif (show(opt, 4)) {\n      \t\toutput(opt, 4, _(\"Merging:\"));\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    - \t\t\toutput_commit_title(opt, iter->item);\n    + \n    + \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n    + \t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n    ++\t\tancestor_name = \"empty tree\";\n    ++\t} else if (ca) {\n    ++\t\tancestor_name = \"merged common ancestors\";\n    ++\t} else {\n    ++\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n    ++\t\t\t\t\t &merged_common_ancestors->object.oid,\n    ++\t\t\t\t\t DEFAULT_ABBREV);\n    ++\t\tancestor_name = merge_base_abbrev.buf;\n      \t}\n      \n    -+\tnum_merge_bases = commit_list_count(ca);\n    - \tmerged_common_ancestors = pop_commit(&ca);\n    - \tif (merged_common_ancestors == NULL) {\n    - \t\t/* if there is no common ancestor, use an empty tree */\n    + \tfor (iter = ca; iter; iter = iter->next) {\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \tif (!opt->call_depth)\n      \t\trepo_read_index(opt->repo);\n      \n     -\topt->ancestor = \"merged common ancestors\";\n    -+\tswitch (num_merge_bases) {\n    -+\tcase 0:\n    -+\t\topt->ancestor = \"<empty tree>\";\n    -+\t\tbreak;\n    -+\tcase 1:\n    -+\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n    -+\t\t\t\t\t &merged_common_ancestors->object.oid,\n    -+\t\t\t\t\t DEFAULT_ABBREV);\n    -+\t\topt->ancestor = merge_base_abbrev.buf;\n    -+\t\tbreak;\n    -+\tdefault:\n    -+\t\topt->ancestor = \"merged common ancestors\";\n    -+\t}\n    ++\topt->ancestor = ancestor_name;\n      \tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n      \t\t\t    get_commit_tree(merged_common_ancestors),\n      \t\t\t    &mrtree);\n    @@ t/t6047-diff3-conflict-markers.sh (new)\n     +\n     +\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 &&\n     +\n    -+\t\tgrep \"|||||| <empty tree>\" content\n    ++\t\tgrep \"|||||| empty tree\" content\n     +\t)\n     +'\n     +\n 5:  19ff6a9503 =  5:  9c0bab2a0b merge-recursive: introduce an enum for detect_directory_renames values\n 6:  5e44146da1 =  6:  2cd1a60a16 merge-recursive: future-proof update_file_flags() against memory leaks\n 7:  df210eb029 !  7:  27a339781f merge-recursive: remove another implicit dependency on the_repository\n    @@ Commit message\n     \n      ## merge-recursive.c ##\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    - \tdefault:\n    - \t\topt->ancestor = \"merged common ancestors\";\n    - \t}\n    + \t\trepo_read_index(opt->repo);\n    + \n    + \topt->ancestor = ancestor_name;\n     -\tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n     -\t\t\t    get_commit_tree(merged_common_ancestors),\n     +\tclean = merge_trees(opt,\n 8:  74dd7b8f59 =  8:  133c470371 Ensure index matches head before invoking merge machinery, round N\n 9:  f04eba4184 !  9:  f43a229ae6 merge-recursive: exit early if index != head\n    @@ merge-recursive.c: static struct commit_list *reverse_commit_list(struct commit_\n      \tstruct commit_list *iter;\n      \tstruct commit *merged_common_ancestors;\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    - \tint num_merge_bases;\n    + \tconst char *ancestor_name;\n      \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n      \n     -\tif (!opt->call_depth)\n    @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \t\t\treturn -1;\n      \t\topt->branch1 = saved_b1;\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n    - \tdefault:\n    - \t\topt->ancestor = \"merged common ancestors\";\n    - \t}\n    + \t\trepo_read_index(opt->repo);\n    + \n    + \topt->ancestor = ancestor_name;\n     -\tclean = merge_trees(opt,\n     -\t\t\t    repo_get_commit_tree(opt->repo, h1),\n     -\t\t\t    repo_get_commit_tree(opt->repo, h2),\n10:  8688d40739 = 10:  678dfbf507 merge-recursive: remove useless parameter in merge_trees()\n11:  a92d460707 = 11:  7b28da5637 merge-recursive: don't force external callers to do our logging\n12:  dec0ea7409 ! 12:  c1ae5cc1c4 cache-tree: share code between functions writing an index as a tree\n    @@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *op\n      \t\t\tinit_merge_options(&o, the_repository);\n      \t\t\to.verbosity = 0;\n     -\t\t\twork = write_tree_from_memory(&o);\n    -+\t\t\twork = write_inmemory_index_as_tree(the_repository);\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    @@ cache-tree.c: static struct cache_tree *cache_tree_find(struct cache_tree *it, c\n      \treturn it;\n      }\n      \n    -+int write_index_as_tree_internal(struct object_id *oid, struct index_state *index_state, int cache_tree_valid, int flags, const char *prefix)\n    ++static int write_index_as_tree_internal(struct object_id *oid,\n    ++\t\t\t\t\tstruct index_state *index_state,\n    ++\t\t\t\t\tint cache_tree_valid,\n    ++\t\t\t\t\tint flags,\n    ++\t\t\t\t\tconst char *prefix)\n     +{\n     +\tif (flags & WRITE_TREE_IGNORE_CACHE_TREE) {\n     +\t\tcache_tree_free(&index_state->cache_tree);\n    @@ cache-tree.c: static struct cache_tree *cache_tree_find(struct cache_tree *it, c\n     +\treturn 0;\n     +}\n     +\n    -+struct tree* write_inmemory_index_as_tree(struct repository *repo) {\n    ++struct tree* write_in_core_index_as_tree(struct repository *repo) {\n     +\tstruct object_id o;\n     +\tint was_valid, ret;\n     +\n    @@ cache-tree.h: void cache_tree_verify(struct repository *, struct index_state *);\n      #define WRITE_TREE_UNMERGED_INDEX (-2)\n      #define WRITE_TREE_PREFIX_ERROR (-3)\n      \n    -+struct tree* write_inmemory_index_as_tree(struct repository *repo);\n    ++struct tree* write_in_core_index_as_tree(struct repository *repo);\n      int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix);\n      void prime_cache_tree(struct repository *, struct index_state *, struct tree *);\n      \n    @@ merge-recursive.c: static int merge_trees_internal(struct merge_options *opt,\n      \n     -\tif (opt->call_depth && !(*result = write_tree_from_memory(opt)))\n     +\tif (opt->call_depth &&\n    -+\t    !(*result = write_inmemory_index_as_tree(opt->repo)))\n    ++\t    !(*result = write_in_core_index_as_tree(opt->repo)))\n      \t\treturn -1;\n      \n      \treturn clean;\n13:  b51f3d1924 ! 13:  2e9fb223d9 merge-recursive: fix some overly long lines\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     -\t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n     +\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n     +\t\t\t\t\t\t\t      tree, \"ancestor\");\n    - \t}\n    - \n    - \tfor (iter = ca; iter; iter = iter->next) {\n    + \t\tancestor_name = \"empty tree\";\n    + \t} else if (ca) {\n    + \t\tancestor_name = \"merged common ancestors\";\n     @@ merge-recursive.c: int merge_recursive(struct merge_options *opt,\n      \treturn clean;\n      }\n14:  a069cc4cca ! 14:  230d903012 merge-recursive: use common name for ancestors/common/base_list\n    @@ merge-recursive.c: static struct commit_list *reverse_commit_list(struct commit_\n     +\tstruct commit *merged_merge_bases;\n      \tstruct tree *mrtree;\n      \tint clean;\n    - \tint num_merge_bases;\n    + \tconst char *ancestor_name;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\toutput_commit_title(opt, h2);\n      \t}\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n      \t\t\toutput_commit_title(opt, iter->item);\n      \t}\n      \n    --\tnum_merge_bases = commit_list_count(ca);\n     -\tmerged_common_ancestors = pop_commit(&ca);\n     -\tif (merged_common_ancestors == NULL) {\n    -+\tnum_merge_bases = commit_list_count(merge_bases);\n     +\tmerged_merge_bases = pop_commit(&merge_bases);\n     +\tif (merged_merge_bases == NULL) {\n      \t\t/* if there is no common ancestor, use an empty tree */\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     -\t\t\t\t\t\t\t      tree, \"ancestor\");\n     +\t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n     +\t\t\t\t\t\t\t \"ancestor\");\n    + \t\tancestor_name = \"empty tree\";\n    +-\t} else if (ca) {\n    ++\t} else if (merge_bases) {\n    + \t\tancestor_name = \"merged common ancestors\";\n    + \t} else {\n    + \t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n    +-\t\t\t\t\t &merged_common_ancestors->object.oid,\n    ++\t\t\t\t\t &merged_merge_bases->object.oid,\n    + \t\t\t\t\t DEFAULT_ABBREV);\n    + \t\tancestor_name = merge_base_abbrev.buf;\n      \t}\n      \n     -\tfor (iter = ca; iter; iter = iter->next) {\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n      \t\t\treturn err(opt, _(\"merge returned no commit\"));\n      \t}\n      \n    -@@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n    - \t\tbreak;\n    - \tcase 1:\n    - \t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n    --\t\t\t\t\t &merged_common_ancestors->object.oid,\n    -+\t\t\t\t\t &merged_merge_bases->object.oid,\n    - \t\t\t\t\t DEFAULT_ABBREV);\n    - \t\topt->ancestor = merge_base_abbrev.buf;\n    - \t\tbreak;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n      \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n15:  93a3ce6b88 ! 15:  28c20a2d08 merge-recursive: rename 'mrtree' to 'result_tree', for clarity\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     -\tstruct tree *mrtree;\n     +\tstruct tree *result_tree;\n      \tint clean;\n    - \tint num_merge_bases;\n    + \tconst char *ancestor_name;\n      \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n16:  1b1df10c11 = 16:  50e2e51cf6 merge-recursive: rename merge_options argument to opt in header\n17:  1526977a85 = 17:  b8cd111bd2 merge-recursive: move some definitions around to clean up the header\n18:  c90f2f15cd ! 18:  9bb8a7f162 merge-recursive: consolidate unnecessary fields in merge_options\n    @@ merge-recursive.c: static int add_cacheinfo(struct merge_options *opt,\n      {\n     -\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n     -\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n    -+\treturn (opt->detect_renames != -1) ? opt->detect_renames : 1;\n    ++\treturn (opt->detect_renames >= 0) ? opt->detect_renames : 1;\n      }\n      \n      static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n    @@ merge-recursive.c: static struct diff_queue_struct *get_diffpairs(struct merge_o\n     -\topts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n     -\t\t\t    opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n     -\t\t\t    1000;\n    -+\topts.rename_limit = (opt->rename_limit != -1) ? opt->rename_limit : 1000;\n    ++\topts.rename_limit = (opt->rename_limit >= 0) ? opt->rename_limit : 1000;\n      \topts.rename_score = opt->rename_score;\n      \topts.show_rename_progress = opt->show_rename_progress;\n      \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n19:  6d930dba72 = 19:  2140490796 merge-recursive: comment and reorder the merge_options fields\n20:  ec3e15f6a8 = 20:  02478e2088 merge-recursive: avoid losing output and leaking memory holding that output\n21:  7edfac7048 ! 21:  2781ca78d7 merge-recursive: split internal fields into a separate struct\n    @@ merge-recursive.c: static int merge_trees_internal(struct merge_options *opt,\n      \n     -\tif (opt->call_depth &&\n     +\tif (opt->priv->call_depth &&\n    - \t    !(*result = write_inmemory_index_as_tree(opt->repo)))\n    + \t    !(*result = write_in_core_index_as_tree(opt->repo)))\n      \t\treturn -1;\n      \n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n    @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt\n     +\tif (!opt->priv->call_depth)\n      \t\trepo_read_index(opt->repo);\n      \n    - \tswitch (num_merge_bases) {\n    + \topt->ancestor = ancestor_name;\n     @@ merge-recursive.c: static int merge_recursive_internal(struct merge_options *opt,\n      \t\treturn clean;\n      \t}\n    @@ merge-recursive.c: static int merge_start(struct merge_options *opt, struct tree\n      \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     -\t\t\t\t       opt->needed_rename_limit, 0);\n     +\t\t\t\t       opt->priv->needed_rename_limit, 0);\n    -+\tfree(opt->priv);\n    -+\topt->priv = NULL;\n    ++\tFREE_AND_NULL(opt->priv);\n      }\n      \n      int merge_trees(struct merge_options *opt,\n22:  9a381873c2 = 22:  5a52aa786e merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*\n23:  c6bc8a196f ! 23:  b7e6c2436a merge-recursive: add sanity checks for relevant merge_options\n    @@ merge-recursive.c: static int merge_start(struct merge_options *opt, struct tree\n     +\t       opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE);\n     +\tassert(opt->rename_limit >= -1);\n     +\tassert(opt->rename_score >= 0 && opt->rename_score <= MAX_SCORE);\n    ++\tassert(opt->show_rename_progress >= 0 && opt->show_rename_progress <= 1);\n     +\n     +\tassert(opt->xdl_opts >= 0);\n     +\tassert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&\n     +\t       opt->recursive_variant <= MERGE_VARIANT_THEIRS);\n     +\n     +\tassert(opt->verbosity >= 0 && opt->verbosity <= 5);\n    -+\tassert(opt->buffer_output >= 0 && opt->buffer_output <= 2);\n    ++\tassert(opt->buffer_output <= 2);\n     +\tassert(opt->obuf.len == 0);\n     +\n     +\tassert(opt->priv == NULL);\n    @@ merge-recursive.c: static int merge_start(struct merge_options *opt, struct tree\n      \tif (repo_index_has_changes(opt->repo, head, &sb)) {\n      \t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n      \t\t    sb.buf);\n    -\n    - ## merge-recursive.h ##\n    -@@ merge-recursive.h: struct merge_options {\n    - \t} detect_directory_renames;\n    - \tint rename_limit;\n    - \tint rename_score;\n    --\tint show_rename_progress;\n    -+\tint show_rename_progress : 1;\n    - \n    - \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n    - \tlong xdl_opts;\n24:  2123e9e4e4 = 24:  6ac9e42a3e merge-recursive: alphabetize include list\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380588","messageId":"20190817184144.32179-3-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 02/24] checkout: provide better conflict hunk description with detached HEAD","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:22Z","receivedAt":"2019-08-17T18:42:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When running 'git checkout -m' and using diff3 style conflict markers,\nwe want all the conflict hunks (left-side, \"common\" or \"merge base\", and\nright-side) to have label markers letting the user know where each came\nfrom.  The \"common\" hunk label (o.ancestor) came from\nold_branch_info->name, but that is NULL when HEAD is detached, which\nresulted in a blank label.  Check for that case and provide an\nabbreviated commit hash instead.\n\n(Incidentally, this was the only case in the git codebase where\nmerge_trees() was called with opt->ancestor being NULL.  A subsequent\ncommit will prevent similar problems by enforcing that merge_trees()\nalways be called with opt->ancestor != NULL.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6123f732a2..d5b946dc3a 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -713,6 +713,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\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@@ -768,6 +769,12 @@ static int merge_working_tree(const struct checkout_opts *opts,\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 == NULL) {\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\tret = merge_trees(&o,\n@@ -781,6 +788,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t\t\t opts, 0,\n \t\t\t\t\t writeout_error);\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-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380589","messageId":"20190817184144.32179-4-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 03/24] merge-recursive: enforce opt->ancestor != NULL when calling merge_trees()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:23Z","receivedAt":"2019-08-17T18:42:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We always want our conflict hunks to be labelled so that users can know\nwhere each came from.  The previous commit fixed the one caller in the\ncodebase which was not setting opt->ancestor (and thus not providing a\nlabel for the \"merge base\" conflict hunk in diff3-style conflict\nmarkers); add an assertion to prevent future codepaths from also\noverlooking this requirement.\n\nEnforcing this requirement also allows us to simplify the code for\nlabelling the conflict hunks by no longer checking if the ancestor label\nis NULL.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 19 +++++++++----------\n 1 file changed, 9 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1d960fa64b..a67ea4957a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1019,7 +1019,7 @@ static int merge_3way(struct merge_options *opt,\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n-\tchar *base_name, *name1, *name2;\n+\tchar *base, *name1, *name2;\n \tint merge_status;\n \n \tll_opts.renormalize = opt->renormalize;\n@@ -1043,16 +1043,13 @@ static int merge_3way(struct merge_options *opt,\n \t\t}\n \t}\n \n-\tassert(a->path && b->path && o->path);\n-\tif (strcmp(a->path, b->path) ||\n-\t    (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s:%s\", opt->ancestor, o->path);\n+\tassert(a->path && b->path && o->path && opt->ancestor);\n+\tif (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {\n+\t\tbase  = mkpathdup(\"%s:%s\", opt->ancestor, o->path);\n \t\tname1 = mkpathdup(\"%s:%s\", branch1, a->path);\n \t\tname2 = mkpathdup(\"%s:%s\", branch2, b->path);\n \t} else {\n-\t\tbase_name = opt->ancestor == NULL ? NULL :\n-\t\t\tmkpathdup(\"%s\", opt->ancestor);\n+\t\tbase  = mkpathdup(\"%s\", opt->ancestor);\n \t\tname1 = mkpathdup(\"%s\", branch1);\n \t\tname2 = mkpathdup(\"%s\", branch2);\n \t}\n@@ -1061,11 +1058,11 @@ static int merge_3way(struct merge_options *opt,\n \tread_mmblob(&src1, &a->oid);\n \tread_mmblob(&src2, &b->oid);\n \n-\tmerge_status = ll_merge(result_buf, a->path, &orig, base_name,\n+\tmerge_status = ll_merge(result_buf, a->path, &orig, base,\n \t\t\t\t&src1, name1, &src2, name2,\n \t\t\t\topt->repo->index, &ll_opts);\n \n-\tfree(base_name);\n+\tfree(base);\n \tfree(name1);\n \tfree(name2);\n \tfree(orig.ptr);\n@@ -3390,6 +3387,8 @@ int merge_trees(struct merge_options *opt,\n \tint code, clean;\n \tstruct strbuf sb = STRBUF_INIT;\n \n+\tassert(opt->ancestor != NULL);\n+\n \tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n \t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n \t\t    sb.buf);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380590","messageId":"20190817184144.32179-5-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 04/24] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:24Z","receivedAt":"2019-08-17T18:42:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 7ca56aa07619 (\"merge-recursive: add a label for ancestor\",\n2010-03-20), a label was added for the '||||||' line to make it have\nthe more informative heading '|||||| merged common ancestors', with\nthe statement:\n\n    It would be nicer to use a more informative label.  Perhaps someone\n    will provide one some day.\n\nThis chosen label was perfectly reasonable when recursiveness kicks in,\ni.e. when there are multiple merge bases.  (I can't think of a better\nlabel in such cases.)  But it is actually somewhat misleading when there\nis a unique merge base or no merge base.  Change this based on the\nnumber of merge bases:\n    >=2: \"merged common ancestors\"\n    1:   <abbreviated commit hash>\n    0:   \"<empty tree>\"\n\nTests have also been added to check that we get the right ancestor name\nfor each of the three cases.\n\nAlso, since merge_recursive() and merge_trees() have polar opposite\npre-conditions for opt->ancestor, document merge_recursive()'s\npre-condition with an assertion.  (An assertion was added to\nmerge_trees() already a few commits ago.)  The differences in\npre-conditions stem from two factors: (1) merge_trees() does not recurse\nand thus does not have multiple sub-merges to worry about -- each of\nwhich would require a different value for opt->ancestor, (2)\nmerge_trees() is only passed trees rather than commits and thus cannot\ninternally guess as good of a label.  Thus, while external callers of\nmerge_trees() are required to provide a non-NULL opt->ancestor,\nmerge_recursive() expects to set this value itself.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |  16 ++-\n t/t6036-recursive-corner-cases.sh |   8 +-\n t/t6047-diff3-conflict-markers.sh | 189 ++++++++++++++++++++++++++++++\n 3 files changed, 209 insertions(+), 4 deletions(-)\n create mode 100755 t/t6047-diff3-conflict-markers.sh\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a67ea4957a..e6b84db2ef 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3507,6 +3507,11 @@ int merge_recursive(struct merge_options *opt,\n \tstruct commit *merged_common_ancestors;\n \tstruct tree *mrtree;\n \tint clean;\n+\tconst char *ancestor_name;\n+\tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n+\n+\tif (!opt->call_depth)\n+\t\tassert(opt->ancestor == NULL);\n \n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n@@ -3535,6 +3540,14 @@ int merge_recursive(struct merge_options *opt,\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n \t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n+\t\tancestor_name = \"empty tree\";\n+\t} else if (ca) {\n+\t\tancestor_name = \"merged common ancestors\";\n+\t} else {\n+\t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n+\t\t\t\t\t &merged_common_ancestors->object.oid,\n+\t\t\t\t\t DEFAULT_ABBREV);\n+\t\tancestor_name = merge_base_abbrev.buf;\n \t}\n \n \tfor (iter = ca; iter; iter = iter->next) {\n@@ -3568,10 +3581,11 @@ int merge_recursive(struct merge_options *opt,\n \tif (!opt->call_depth)\n \t\trepo_read_index(opt->repo);\n \n-\topt->ancestor = \"merged common ancestors\";\n+\topt->ancestor = ancestor_name;\n \tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n \t\t\t    get_commit_tree(merged_common_ancestors),\n \t\t\t    &mrtree);\n+\tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d23b948f27..7fddcc8c73 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1562,6 +1562,7 @@ test_expect_success 'check nested conflicts' '\n \t\tcd nested_conflicts &&\n \n \t\tgit clean -f &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L2^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1582,7 +1583,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:a >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1594,7 +1595,7 @@ test_expect_success 'check nested conflicts' '\n \t\tgit cat-file -p R1:b >theirs &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tours  \\\n \t\t\tbase  \\\n@@ -1732,6 +1733,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t(\n \t\tcd virtual_merge_base_has_nested_conflicts &&\n \n+\t\tMASTER=$(git rev-parse --short master) &&\n \t\tgit checkout L3^0 &&\n \n \t\t# Merge must fail; there is a conflict\n@@ -1760,7 +1762,7 @@ test_expect_success 'check virtual merge base with nested conflicts' '\n \t\tcp left merged-once &&\n \t\ttest_must_fail git merge-file --diff3 \\\n \t\t\t-L \"Temporary merge branch 1\" \\\n-\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"$MASTER\"  \\\n \t\t\t-L \"Temporary merge branch 2\" \\\n \t\t\tmerged-once \\\n \t\t\tbase        \\\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nnew file mode 100755\nindex 0000000000..3fb68e0aae\n--- /dev/null\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -0,0 +1,189 @@\n+#!/bin/sh\n+\n+test_description='recursive merge diff3 style conflict markers'\n+\n+. ./test-lib.sh\n+\n+# Setup:\n+#          L1\n+#            \\\n+#             ?\n+#            /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 both have a file named 'content' but have no common history\n+#\n+\n+test_expect_success 'setup no merge base' '\n+\ttest_create_repo no_merge_base &&\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout -b L &&\n+\t\ttest_commit A content A &&\n+\n+\t\tgit checkout --orphan R &&\n+\t\ttest_commit B content B\n+\t)\n+'\n+\n+test_expect_success 'check no merge base' '\n+\t(\n+\t\tcd no_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| empty tree\" content\n+\t)\n+'\n+\n+# Setup:\n+#          L1\n+#         /  \\\n+#   master    ?\n+#         \\  /\n+#          R1\n+#\n+# Where:\n+#   L1 and R1 have modified the same file ('content') in conflicting ways\n+#\n+\n+test_expect_success 'setup unique merge base' '\n+\ttest_create_repo unique_merge_base &&\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\ttest_commit base content \"1\n+2\n+3\n+4\n+5\n+\" &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\tgit checkout L &&\n+\t\ttest_commit L content \"1\n+2\n+3\n+4\n+5\n+7\" &&\n+\n+\t\tgit checkout R &&\n+\t\tgit rm content &&\n+\t\ttest_commit R renamed \"1\n+2\n+3\n+4\n+5\n+six\"\n+\t)\n+'\n+\n+test_expect_success 'check unique merge base' '\n+\t(\n+\t\tcd unique_merge_base &&\n+\n+\t\tgit checkout L^0 &&\n+\t\tMASTER=$(git rev-parse --short master) &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| $MASTER:content\" renamed\n+\t)\n+'\n+\n+# Setup:\n+#          L1---L2--L3\n+#         /  \\ /      \\\n+#   master    X1       ?\n+#         \\  / \\      /\n+#          R1---R2--R3\n+#\n+# Where:\n+#   commits L1 and R1 have modified the same file in non-conflicting ways\n+#   X1 is an auto-generated merge-base used when merging L1 and R1\n+#   commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively\n+#   commits L3 and R3 both modify 'content' in conflicting ways\n+#\n+\n+test_expect_success 'setup multiple merge bases' '\n+\ttest_create_repo multiple_merge_bases &&\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\ttest_commit initial content \"1\n+2\n+3\n+4\n+5\" &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\ttest_commit L1 content \"0\n+1\n+2\n+3\n+4\n+5\" &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\ttest_commit R1 content \"1\n+2\n+3\n+4\n+5\n+6\" &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\tgit merge R1 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\tgit merge L1 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_commit L3 content \"0\n+1\n+2\n+3\n+4\n+5\n+A\" &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\tgit rm content &&\n+\t\ttest_commit R3 renamed \"0\n+2\n+3\n+4\n+5\n+six\"\n+\t)\n+'\n+\n+test_expect_success 'check multiple merge bases' '\n+\t(\n+\t\tcd multiple_merge_bases &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 &&\n+\n+\t\tgrep \"|||||| merged common ancestors:content\" renamed\n+\t)\n+'\n+\n+test_done\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380591","messageId":"20190817184144.32179-6-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 05/24] merge-recursive: introduce an enum for detect_directory_renames values","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:25Z","receivedAt":"2019-08-17T18:42:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Derrick Stolee <dstolee@microsoft.com>\n\nImprove code readability by introducing an enum to replace the\nnot-quite-boolean values taken on by detect_directory_renames.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/am.c      |  2 +-\n merge-recursive.c | 24 +++++++++++++++---------\n merge-recursive.h |  6 +++++-\n 3 files changed, 21 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 1aea657a7f..037e828efe 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1538,7 +1538,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \to.branch1 = \"HEAD\";\n \ttheir_tree_name = xstrfmt(\"%.*s\", linelen(state->msg), state->msg);\n \to.branch2 = their_tree_name;\n-\to.detect_directory_renames = 0;\n+\to.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;\n \n \tif (state->quiet)\n \t\to.verbosity = 0;\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e6b84db2ef..9622781612 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1375,7 +1375,8 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \tconst struct rename *ren = ci->ren1;\n \tconst struct diff_filespec *dest = ren->pair->two;\n \tchar *file_path = dest->path;\n-\tint mark_conflicted = (opt->detect_directory_renames == 1);\n+\tint mark_conflicted = (opt->detect_directory_renames ==\n+\t\t\t       MERGE_DIRECTORY_RENAMES_CONFLICT);\n \tassert(ren->dir_rename_original_dest);\n \n \tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n@@ -2860,8 +2861,9 @@ static int detect_and_process_renames(struct merge_options *opt,\n \thead_pairs = get_diffpairs(opt, common, head);\n \tmerge_pairs = get_diffpairs(opt, common, merge);\n \n-\tif ((opt->detect_directory_renames == 2) ||\n-\t    (opt->detect_directory_renames == 1 && !opt->call_depth)) {\n+\tif ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) ||\n+\t    (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n+\t     !opt->call_depth)) {\n \t\tdir_re_head = get_directory_renames(head_pairs);\n \t\tdir_re_merge = get_directory_renames(merge_pairs);\n \n@@ -3119,7 +3121,8 @@ static int handle_rename_normal(struct merge_options *opt,\n \tclean = handle_content_merge(&mfi, opt, path, was_dirty(opt, path),\n \t\t\t\t     o, a, b, ci);\n \n-\tif (clean && opt->detect_directory_renames == 1 &&\n+\tif (clean &&\n+\t    opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n \t    ren->dir_rename_original_dest) {\n \t\tif (update_stages(opt, path,\n \t\t\t\t  NULL,\n@@ -3164,12 +3167,12 @@ static int warn_about_dir_renamed_entries(struct merge_options *opt,\n \t\treturn clean;\n \n \t/* Sanity checks */\n-\tassert(opt->detect_directory_renames > 0);\n+\tassert(opt->detect_directory_renames > MERGE_DIRECTORY_RENAMES_NONE);\n \tassert(ren->dir_rename_original_type == 'A' ||\n \t       ren->dir_rename_original_type == 'R');\n \n \t/* Check whether to treat directory renames as a conflict */\n-\tclean = (opt->detect_directory_renames == 2);\n+\tclean = (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE);\n \n \tis_add = (ren->dir_rename_original_type == 'A');\n \tif (ren->dir_rename_original_type == 'A' && clean) {\n@@ -3679,9 +3682,12 @@ static void merge_recursive_config(struct merge_options *opt)\n \tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n \t\tint boolval = git_parse_maybe_bool(value);\n \t\tif (0 <= boolval) {\n-\t\t\topt->detect_directory_renames = boolval ? 2 : 0;\n+\t\t\topt->detect_directory_renames = boolval ?\n+\t\t\t\tMERGE_DIRECTORY_RENAMES_TRUE :\n+\t\t\t\tMERGE_DIRECTORY_RENAMES_NONE;\n \t\t} else if (!strcasecmp(value, \"conflict\")) {\n-\t\t\topt->detect_directory_renames = 1;\n+\t\t\topt->detect_directory_renames =\n+\t\t\t\tMERGE_DIRECTORY_RENAMES_CONFLICT;\n \t\t} /* avoid erroring on values from future versions of git */\n \t\tfree(value);\n \t}\n@@ -3701,7 +3707,7 @@ void init_merge_options(struct merge_options *opt,\n \topt->renormalize = 0;\n \topt->diff_detect_rename = -1;\n \topt->merge_detect_rename = -1;\n-\topt->detect_directory_renames = 1;\n+\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex c2b7bb65c6..f1b6ef38ae 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -22,7 +22,11 @@ struct merge_options {\n \tunsigned renormalize : 1;\n \tlong xdl_opts;\n \tint verbosity;\n-\tint detect_directory_renames;\n+\tenum {\n+\t\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n+\t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n+\t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n+\t} detect_directory_renames;\n \tint diff_detect_rename;\n \tint merge_detect_rename;\n \tint diff_rename_limit;\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380592","messageId":"20190817184144.32179-7-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 06/24] merge-recursive: future-proof update_file_flags() against memory leaks","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:26Z","receivedAt":"2019-08-17T18:42:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There is a 'free_buf' label to which all but one of the error paths in\nupdate_file_flags() jump; that error case involves a NULL buf and is\nthus not a memory leak.  However, make that error case execute the same\ndeallocation code anyway so that if anyone adds any additional memory\nallocations or deallocations, then all error paths correctly deallocate\nresources.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 9622781612..1d4df952e5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -934,9 +934,11 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \n \t\tbuf = read_object_file(&contents->oid, &type, &size);\n-\t\tif (!buf)\n-\t\t\treturn err(opt, _(\"cannot read object %s '%s'\"),\n-\t\t\t\t   oid_to_hex(&contents->oid), path);\n+\t\tif (!buf) {\n+\t\t\tret = err(opt, _(\"cannot read object %s '%s'\"),\n+\t\t\t\t  oid_to_hex(&contents->oid), path);\n+\t\t\tgoto free_buf;\n+\t\t}\n \t\tif (type != OBJ_BLOB) {\n \t\t\tret = err(opt, _(\"blob expected for %s '%s'\"),\n \t\t\t\t  oid_to_hex(&contents->oid), path);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380594","messageId":"20190817184144.32179-10-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 09/24] merge-recursive: exit early if index != head","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:29Z","receivedAt":"2019-08-17T18:42:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We had a rule to enforce that the index matches head, but it was found\nat the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially huge amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nFurther, not enforcing this requirement earlier allowed other bugs (such\nas an unintentional unconditional dropping and reloading of the index in\nmerge_recursive() even when no recursion was necessary), to mask bugs in\nother callers (which were fixed in the commit prior to this one).\n\nMake sure we do the index == head check at the beginning of the merge,\nand error out immediately if it fails.  While we're at it, fix a small\nleak in the show-the-error codepath.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 101 +++++++++++++++++++++++++++++++++-------------\n 1 file changed, 72 insertions(+), 29 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 88a33e6e72..2a254d5563 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3382,23 +3382,14 @@ static int process_entry(struct merge_options *opt,\n \treturn clean_merge;\n }\n \n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+static int merge_trees_internal(struct merge_options *opt,\n+\t\t\t\tstruct tree *head,\n+\t\t\t\tstruct tree *merge,\n+\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tassert(opt->ancestor != NULL);\n-\n-\tif (!opt->call_depth && repo_index_has_changes(opt->repo, head, &sb)) {\n-\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n-\t\t    sb.buf);\n-\t\treturn -1;\n-\t}\n \n \tif (opt->subtree_shift) {\n \t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n@@ -3502,11 +3493,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n  * Merge the commits h1 and h2, return the resulting virtual\n  * commit object and a flag indicating the cleanness of the merge.\n  */\n-int merge_recursive(struct merge_options *opt,\n-\t\t    struct commit *h1,\n-\t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n-\t\t    struct commit **result)\n+static int merge_recursive_internal(struct merge_options *opt,\n+\t\t\t\t    struct commit *h1,\n+\t\t\t\t    struct commit *h2,\n+\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_common_ancestors;\n@@ -3515,9 +3506,6 @@ int merge_recursive(struct merge_options *opt,\n \tconst char *ancestor_name;\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n \n-\tif (!opt->call_depth)\n-\t\tassert(opt->ancestor == NULL);\n-\n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n \t\toutput_commit_title(opt, h1);\n@@ -3571,7 +3559,7 @@ int merge_recursive(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive(opt, merged_common_ancestors, iter->item,\n+\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n \t\t\t\t    NULL, &merged_common_ancestors) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n@@ -3587,12 +3575,12 @@ int merge_recursive(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = ancestor_name;\n-\tclean = merge_trees(opt,\n-\t\t\t    repo_get_commit_tree(opt->repo, h1),\n-\t\t\t    repo_get_commit_tree(opt->repo, h2),\n-\t\t\t    repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t merged_common_ancestors),\n-\t\t\t    &mrtree);\n+\tclean = merge_trees_internal(opt,\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n+\t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n+\t\t\t\t     repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t     &mrtree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n@@ -3613,6 +3601,61 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n+static int merge_start(struct merge_options *opt, struct tree *head)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void merge_finalize(struct merge_options *opt)\n+{\n+\t/* Common code for wrapping up merges will be added here later */\n+}\n+\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *common,\n+\t\tstruct tree **result)\n+{\n+\tint clean;\n+\n+\tassert(opt->ancestor != NULL);\n+\n+\tif (merge_start(opt, head))\n+\t\treturn -1;\n+\tclean = merge_trees_internal(opt, head, merge, common, result);\n+\tmerge_finalize(opt);\n+\n+\treturn clean;\n+}\n+\n+int merge_recursive(struct merge_options *opt,\n+\t\t    struct commit *h1,\n+\t\t    struct commit *h2,\n+\t\t    struct commit_list *ca,\n+\t\t    struct commit **result)\n+{\n+\tint clean;\n+\n+\tassert(opt->ancestor == NULL);\n+\n+\tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n+\t\treturn -1;\n+\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tmerge_finalize(opt);\n+\n+\treturn clean;\n+}\n+\n static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n \t\t\t      const char *name)\n {\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380593","messageId":"20190817184144.32179-9-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 08/24] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:28Z","receivedAt":"2019-08-17T18:42:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is the bug that just won't die; there always seems to be another\nform of it somewhere.  See the commit message of 55f39cf7551b (\"merge:\nfix misleading pre-merge check documentation\", 2018-06-30) for a more\ndetailed explanation), but in short:\n\n<quick summary>\n\nbuiltin/merge.c contains this important requirement for merge\nstrategies:\n\n    ...the index must be in sync with the head commit.  The strategies are\n    responsible to ensure this.\n\nThis condition is important to enforce because there are two likely\nfailure cases when the index isn't in sync with the head commit:\n\n  * we silently throw away changes the user had staged before the merge\n\n  * we accidentally (and silently) include changes in the merge that\n    were not part of either of the branches/trees being merged\n\nDiscarding users' work and mis-merging are both bad outcomes, especially\nwhen done silently, so naturally this rule was stated sternly -- but,\nunfortunately totally ignored in practice unless and until actual bugs\nwere found.  But, fear not: the bugs from this were fixed in commit\n  ee6566e8d70d (\"[PATCH] Rewrite read-tree\", 2005-09-05)\nthrough a rewrite of read-tree (again, commit 55f39cf7551b has a more\ndetailed explanation of how this affected merge).  And it was fixed\nagain in commit\n  160252f81626 (\"git-merge-ours: make sure our index matches HEAD\", 2005-11-03)\n...and it was fixed again in commit\n  3ec62ad9ffba (\"merge-octopus: abort if index does not match HEAD\", 2016-04-09)\n...and again in commit\n  65170c07d466 (\"merge-recursive: avoid incorporating uncommitted changes in a merge\", 2017-12-21)\n...and again in commit\n  eddd1a411d93 (\"merge-recursive: enforce rule that index matches head before merging\", 2018-06-30)\n\n...with multiple testcases added to the testsuite that could be\nenumerated in even more commits.\n\nThen, finally, in a patch in the same series as the last fix above, the\ndocumentation about this requirement was fixed in commit 55f39cf7551b\n(\"merge: fix misleading pre-merge check documentation\", 2018-06-30), and\nwe all lived happily ever after...\n\n</quick summary>\n\nUnfortunately, \"ever after\" apparently denotes a limited time and it\nexpired today.  The merge-recursive rule to enforce that index matches\nhead was at the beginning of merge_trees() and would only trigger when\nopt->call_depth was 0.  Since merge_recursive() doesn't call\nmerge_trees() until after returning from recursing, this meant that the\ncheck wasn't triggered by merge_recursive() until it had first finished\nall the intermediate merges to create virtual merge bases.  That is a\npotentially HUGE amount of computation (and writing of intermediate\nmerge results into the .git/objects directory) before it errors out and\nsays, in effect, \"Sorry, I can't do any merging because you have some\nlocal changes that would be overwritten.\"\n\nTrying to enforce that all of merge_trees(), merge_recursive(), and\nmerge_recursive_generic() checked the index == head condition earlier\nresulted in a bunch of broken tests.  It turns out that\nmerge_recursive() has code to drop and reload the cache while recursing\nto create intermediate virtual merge bases, but unfortunately that code\nruns even when no recursion is necessary.  This unconditional dropping\nand reloading of the cache masked a few bugs:\n\n  * builtin/merge-recursive.c: didn't even bother loading the index.\n\n  * builtin/stash.c: feels like a fake 'builtin' because it repeatedly\n    invokes git subprocesses all over the place, mixed with other\n    operations.  In particular, invoking \"git reset\" will reset the\n    index on disk, but the parent process that invoked it won't\n    automatically have its in-memory index updated.\n\n  * t3030-merge-recursive.h: this test has always been broken in that it\n    didn't make sure to make index match head before running.  But, it\n    didn't care about the index or even the merge result, just the\n    verbose output while running.  While commit eddd1a411d93\n    (\"merge-recursive: enforce rule that index matches head before\n    merging\", 2018-06-30) should have uncovered this broken test, it\n    used a test_must_fail wrapper around the merge-recursive call\n    because it was known that the merge resulted in a rename/rename\n    conflict.  Thus, that fix only made this test fail for a different\n    reason, and since the index == head check didn't happen until after\n    coming all the way back out of the recursion, the testcase had\n    enough information to pass the one check that it did perform.\n\nSo, load the index in builtin/merge-recursive.c, reload the in-memory\nindex in builtin/stash.c, and modify the t3030 testcase to correctly\nsetup the index and make sure that the test fails in the expected way\n(meaning it reports a rename/rename conflict).  This makes sure that\nall callers actually make the index match head.  The next commit will\nthen enforce the condition that index matches head earlier so this\nproblem doesn't return in the future.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-recursive.c  | 4 ++++\n builtin/stash.c            | 2 ++\n t/t3030-merge-recursive.sh | 9 ++++++++-\n 3 files changed, 14 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex 5b910e351e..a4bfd8fc51 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -1,3 +1,4 @@\n+#include \"cache.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n #include \"tag.h\"\n@@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (argc - i != 3) /* \"--\" \"<head>\" \"<remote>\" */\n \t\tdie(_(\"not handling anything other than two heads merge.\"));\n \n+\tif (repo_read_index_unmerged(the_repository))\n+\t\tdie_resolve_conflict(\"merge\");\n+\n \to.branch1 = argv[++i];\n \to.branch2 = argv[++i];\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex b5a301f24d..4aa47785f9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,\n \t\t\t\treturn error(_(\"could not save index tree\"));\n \n \t\t\treset_head();\n+\t\t\tdiscard_cache();\n+\t\t\tread_cache();\n \t\t}\n \t}\n \ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex ff641b348a..a37bcc58a0 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -667,15 +667,22 @@ test_expect_success 'merging with triple rename across D/F conflict' '\n test_expect_success 'merge-recursive remembers the names of all base trees' '\n \tgit reset --hard HEAD &&\n \n+\t# make the index match $c1 so that merge-recursive below does not\n+\t# fail early\n+\tgit diff --binary HEAD $c1 -- | git apply --cached &&\n+\n \t# more trees than static slots used by oid_to_hex()\n \tfor commit in $c0 $c2 $c4 $c5 $c6 $c7\n \tdo\n \t\tgit rev-parse \"$commit^{tree}\"\n \tdone >trees &&\n \n-\t# ignore the return code -- it only fails because the input is weird\n+\t# ignore the return code; it only fails because the input is weird...\n \ttest_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out &&\n \n+\t# ...but make sure it fails in the expected way\n+\ttest_i18ngrep CONFLICT.*rename/rename out &&\n+\n \t# merge-recursive prints in reverse order, but we do not care\n \tsort <trees >expect &&\n \tsed -n \"s/^virtual //p\" out | sort >actual &&\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380595","messageId":"20190817184144.32179-11-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 10/24] merge-recursive: remove useless parameter in merge_trees()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:30Z","receivedAt":"2019-08-17T18:42:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees() took a results parameter that would only be written when\nopt->call_depth was positive, which is never the case now that\nmerge_trees_internal() has been split from merge_trees().  Remove the\nmisleading and unused parameter from merge_trees().\n\nWhile at it, add some comments explaining how the output of\nmerge_trees() and merge_recursive() differ.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  4 +---\n merge-recursive.c  |  6 +++---\n merge-recursive.h  | 20 ++++++++++++++++----\n sequencer.c        |  4 ++--\n 4 files changed, 22 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex d5b946dc3a..90e0eaf25e 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -708,7 +708,6 @@ static int merge_working_tree(const struct checkout_opts *opts,\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 *result;\n \t\t\tstruct tree *work;\n \t\t\tstruct tree *old_tree;\n \t\t\tstruct merge_options o;\n@@ -780,8 +779,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\tret = merge_trees(&o,\n \t\t\t\t\t  new_tree,\n \t\t\t\t\t  work,\n-\t\t\t\t\t  old_tree,\n-\t\t\t\t\t  &result);\n+\t\t\t\t\t  old_tree);\n \t\t\tif (ret < 0)\n \t\t\t\texit(128);\n \t\t\tret = reset_tree(new_tree,\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 2a254d5563..4ce783dbfa 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3623,16 +3623,16 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result)\n+\t\tstruct tree *common)\n {\n \tint clean;\n+\tstruct tree *ignored;\n \n \tassert(opt->ancestor != NULL);\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tclean = merge_trees_internal(opt, head, merge, common, result);\n+\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n \tmerge_finalize(opt);\n \n \treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex f1b6ef38ae..18012fff9d 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -74,19 +74,31 @@ static inline int merge_detect_rename(struct merge_options *o)\n \t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n }\n \n-/* merge_trees() but with recursive ancestor consolidation */\n+/*\n+ * merge_recursive is like merge_trees() but with recursive ancestor\n+ * consolidation, and when successful, it creates an actual commit\n+ * and writes its address to *result.\n+ *\n+ * NOTE: empirically, about a decade ago it was determined that with more\n+ *       than two merge bases, optimal behavior was found when the\n+ *       ancestors were passed in the order of oldest merge base to newest\n+ *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n+ *       you need it.\n+ */\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *ancestors,\n \t\t    struct commit **result);\n \n-/* rename-detecting three-way merge, no recursion */\n+/*\n+ * rename-detecting three-way merge, no recursion; result of merge is written\n+ * to opt->repo->index.\n+ */\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common,\n-\t\tstruct tree **result);\n+\t\tstruct tree *common);\n \n /*\n  * \"git-merge-recursive\" can be fed trees; wrap them into\ndiff --git a/sequencer.c b/sequencer.c\nindex 34ebf8ed94..c4ed30f1b4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -586,7 +586,7 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n-\tstruct tree *result, *next_tree, *base_tree, *head_tree;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n \tint clean;\n \tchar **xopt;\n \tstruct lock_file index_lock = LOCK_INIT;\n@@ -613,7 +613,7 @@ static int do_recursive_merge(struct repository *r,\n \n \tclean = merge_trees(&o,\n \t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree, &result);\n+\t\t\t    next_tree, base_tree);\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380596","messageId":"20190817184144.32179-12-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 11/24] merge-recursive: don't force external callers to do our logging","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:31Z","receivedAt":"2019-08-17T18:42:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Alternatively, you can view this as \"make the merge functions behave\nmore similarly.\"  merge-recursive has three different entry points:\nmerge_trees(), merge_recursive(), and merge_recursive_generic().  Two of\nthese would call diff_warn_rename_limit(), but merge_trees() didn't.\nThis lead to callers of merge_trees() needing to manually call\ndiff_warn_rename_limit() themselves.  Move this to the new\nmerge_finalize() function to make sure that all three entry points run\nthis function.\n\nNote that there are two external callers of merge_trees(), one in\nsequencer.c and one in builtin/checkout.c.  The one in sequencer.c is\ncleaned up by this patch and just transfers where the call to\ndiff_warn_rename_limit() is made; the one in builtin/checkout.c is for\nswitching to a different commit and in the very rare case where the\nwarning might be triggered, it would probably be helpful to include\n(e.g. if someone is modifying a file that has been renamed in moving to\nthe other commit, but there are so many renames between the commits that\nthe limit kicks in and none are detected, it may help to have an\nexplanation about why they got a delete/modify conflict instead of a\nproper content merge in a renamed file).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++----\n sequencer.c       | 1 -\n 2 files changed, 3 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 4ce783dbfa..fda67dd371 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3595,9 +3595,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \tflush_output(opt);\n \tif (!opt->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n-\tif (show(opt, 2))\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n \treturn clean;\n }\n \n@@ -3617,7 +3614,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n-\t/* Common code for wrapping up merges will be added here later */\n+\tif (show(opt, 2))\n+\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t\t       opt->needed_rename_limit, 0);\n }\n \n int merge_trees(struct merge_options *opt,\ndiff --git a/sequencer.c b/sequencer.c\nindex c4ed30f1b4..094a4dd03d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -617,7 +617,6 @@ static int do_recursive_merge(struct repository *r,\n \tif (is_rebase_i(opts) && clean <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-\tdiff_warn_rename_limit(\"merge.renamelimit\", o.needed_rename_limit, 0);\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380597","messageId":"20190817184144.32179-14-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 13/24] merge-recursive: fix some overly long lines","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:33Z","receivedAt":"2019-08-17T18:42:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code change, just add some line breaks to fix lines that\nhave grown in length due to various refactorings.  Most remaining lines\nof excessive length in merge-recursive include error messages and it's\nnot clear that splitting those improves things.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 19 +++++++++++++------\n 1 file changed, 13 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ae509357f7..720678c21d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -681,7 +681,9 @@ static void add_flattened_path(struct strbuf *out, const char *s)\n \t\t\tout->buf[i] = '_';\n }\n \n-static char *unique_path(struct merge_options *opt, const char *path, const char *branch)\n+static char *unique_path(struct merge_options *opt,\n+\t\t\t const char *path,\n+\t\t\t const char *branch)\n {\n \tstruct path_hashmap_entry *entry;\n \tstruct strbuf newpath = STRBUF_INIT;\n@@ -915,7 +917,8 @@ static int update_file_flags(struct merge_options *opt,\n \t\t}\n \t\tif (S_ISREG(contents->mode)) {\n \t\t\tstruct strbuf strbuf = STRBUF_INIT;\n-\t\t\tif (convert_to_working_tree(opt->repo->index, path, buf, size, &strbuf)) {\n+\t\t\tif (convert_to_working_tree(opt->repo->index,\n+\t\t\t\t\t\t    path, buf, size, &strbuf)) {\n \t\t\t\tfree(buf);\n \t\t\t\tsize = strbuf.len;\n \t\t\t\tbuf = strbuf_detach(&strbuf, NULL);\n@@ -3393,7 +3396,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp, NULL, 512);\n+\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n \n@@ -3502,7 +3506,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo, tree, \"ancestor\");\n+\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n+\t\t\t\t\t\t\t      tree, \"ancestor\");\n \t\tancestor_name = \"empty tree\";\n \t} else if (ca) {\n \t\tancestor_name = \"merged common ancestors\";\n@@ -3625,7 +3630,8 @@ int merge_recursive(struct merge_options *opt,\n \treturn clean;\n }\n \n-static struct commit *get_ref(struct repository *repo, const struct object_id *oid,\n+static struct commit *get_ref(struct repository *repo,\n+\t\t\t      const struct object_id *oid,\n \t\t\t      const char *name)\n {\n \tstruct object *object;\n@@ -3660,7 +3666,8 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\tint i;\n \t\tfor (i = 0; i < num_base_list; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i], oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n+\t\t\t\t\t     oid_to_hex(base_list[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n \t\t\t\t\t   oid_to_hex(base_list[i]));\n \t\t\tcommit_list_insert(base, &ca);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380599","messageId":"20190817184144.32179-13-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 12/24] cache-tree: share code between functions writing an index as a tree","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:32Z","receivedAt":"2019-08-17T18:42:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"write_tree_from_memory() appeared to be a merge-recursive special that\nbasically duplicated write_index_as_tree().  The two have a different\nsignature, but the bigger difference was just that write_index_as_tree()\nwould always unconditionally read the index off of disk instead of\nworking on the current in-memory index.  So:\n\n  * split out common code into write_index_as_tree_internal()\n\n  * rename write_tree_from_memory() to write_inmemory_index_as_tree(),\n    make it call write_index_as_tree_internal(), and move it to\n    cache-tree.c\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/checkout.c |  2 +-\n cache-tree.c       | 85 +++++++++++++++++++++++++++++++++-------------\n cache-tree.h       |  3 +-\n merge-recursive.c  | 34 ++-----------------\n merge-recursive.h  |  1 -\n 5 files changed, 67 insertions(+), 58 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 90e0eaf25e..5e41fc1c01 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -760,7 +760,7 @@ static int merge_working_tree(const struct checkout_opts *opts,\n \t\t\t */\n \t\t\tinit_merge_options(&o, the_repository);\n \t\t\to.verbosity = 0;\n-\t\t\twork = write_tree_from_memory(&o);\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,\ndiff --git a/cache-tree.c b/cache-tree.c\nindex 706ffcf188..fbb5252521 100644\n--- a/cache-tree.c\n+++ b/cache-tree.c\n@@ -608,11 +608,66 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat\n \treturn it;\n }\n \n+static int write_index_as_tree_internal(struct object_id *oid,\n+\t\t\t\t\tstruct index_state *index_state,\n+\t\t\t\t\tint cache_tree_valid,\n+\t\t\t\t\tint flags,\n+\t\t\t\t\tconst char *prefix)\n+{\n+\tif (flags & WRITE_TREE_IGNORE_CACHE_TREE) {\n+\t\tcache_tree_free(&index_state->cache_tree);\n+\t\tcache_tree_valid = 0;\n+\t}\n+\n+\tif (!index_state->cache_tree)\n+\t\tindex_state->cache_tree = cache_tree();\n+\n+\tif (!cache_tree_valid && cache_tree_update(index_state, flags) < 0)\n+\t\treturn WRITE_TREE_UNMERGED_INDEX;\n+\n+\tif (prefix) {\n+\t\tstruct cache_tree *subtree;\n+\t\tsubtree = cache_tree_find(index_state->cache_tree, prefix);\n+\t\tif (!subtree)\n+\t\t\treturn WRITE_TREE_PREFIX_ERROR;\n+\t\toidcpy(oid, &subtree->oid);\n+\t}\n+\telse\n+\t\toidcpy(oid, &index_state->cache_tree->oid);\n+\n+\treturn 0;\n+}\n+\n+struct tree* write_in_core_index_as_tree(struct repository *repo) {\n+\tstruct object_id o;\n+\tint was_valid, ret;\n+\n+\tstruct index_state *index_state\t= repo->index;\n+\twas_valid = index_state->cache_tree &&\n+\t\t    cache_tree_fully_valid(index_state->cache_tree);\n+\n+\tret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL);\n+\tif (ret == WRITE_TREE_UNMERGED_INDEX) {\n+\t\tint i;\n+\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n+\t\tfor (i = 0; i < index_state->cache_nr; i++) {\n+\t\t\tconst struct cache_entry *ce = index_state->cache[i];\n+\t\t\tif (ce_stage(ce))\n+\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n+\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n+\t\t}\n+\t\tBUG(\"unmerged index entries when writing inmemory index\");\n+\t}\n+\n+\treturn lookup_tree(repo, &index_state->cache_tree->oid);\n+}\n+\n+\n int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix)\n {\n \tint entries, was_valid;\n \tstruct lock_file lock_file = LOCK_INIT;\n-\tint ret = 0;\n+\tint ret;\n \n \thold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);\n \n@@ -621,18 +676,14 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \t\tret = WRITE_TREE_UNREADABLE_INDEX;\n \t\tgoto out;\n \t}\n-\tif (flags & WRITE_TREE_IGNORE_CACHE_TREE)\n-\t\tcache_tree_free(&index_state->cache_tree);\n \n-\tif (!index_state->cache_tree)\n-\t\tindex_state->cache_tree = cache_tree();\n+\twas_valid = !(flags & WRITE_TREE_IGNORE_CACHE_TREE) &&\n+\t\t    index_state->cache_tree &&\n+\t\t    cache_tree_fully_valid(index_state->cache_tree);\n \n-\twas_valid = cache_tree_fully_valid(index_state->cache_tree);\n-\tif (!was_valid) {\n-\t\tif (cache_tree_update(index_state, flags) < 0) {\n-\t\t\tret = WRITE_TREE_UNMERGED_INDEX;\n-\t\t\tgoto out;\n-\t\t}\n+\tret = write_index_as_tree_internal(oid, index_state, was_valid, flags,\n+\t\t\t\t\t   prefix);\n+\tif (!ret && !was_valid) {\n \t\twrite_locked_index(index_state, &lock_file, COMMIT_LOCK);\n \t\t/* Not being able to write is fine -- we are only interested\n \t\t * in updating the cache-tree part, and if the next caller\n@@ -642,18 +693,6 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,\n \t\t */\n \t}\n \n-\tif (prefix) {\n-\t\tstruct cache_tree *subtree;\n-\t\tsubtree = cache_tree_find(index_state->cache_tree, prefix);\n-\t\tif (!subtree) {\n-\t\t\tret = WRITE_TREE_PREFIX_ERROR;\n-\t\t\tgoto out;\n-\t\t}\n-\t\toidcpy(oid, &subtree->oid);\n-\t}\n-\telse\n-\t\toidcpy(oid, &index_state->cache_tree->oid);\n-\n out:\n \trollback_lock_file(&lock_file);\n \treturn ret;\ndiff --git a/cache-tree.h b/cache-tree.h\nindex 757bbc48bc..639bfa5340 100644\n--- a/cache-tree.h\n+++ b/cache-tree.h\n@@ -34,7 +34,7 @@ int cache_tree_fully_valid(struct cache_tree *);\n int cache_tree_update(struct index_state *, int);\n void cache_tree_verify(struct repository *, struct index_state *);\n \n-/* bitmasks to write_cache_as_tree flags */\n+/* bitmasks to write_index_as_tree flags */\n #define WRITE_TREE_MISSING_OK 1\n #define WRITE_TREE_IGNORE_CACHE_TREE 2\n #define WRITE_TREE_DRY_RUN 4\n@@ -46,6 +46,7 @@ void cache_tree_verify(struct repository *, struct index_state *);\n #define WRITE_TREE_UNMERGED_INDEX (-2)\n #define WRITE_TREE_PREFIX_ERROR (-3)\n \n+struct tree* write_in_core_index_as_tree(struct repository *repo);\n int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix);\n void prime_cache_tree(struct repository *, struct index_state *, struct tree *);\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fda67dd371..ae509357f7 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -412,37 +412,6 @@ static void unpack_trees_finish(struct merge_options *opt)\n \tclear_unpack_trees_porcelain(&opt->unpack_opts);\n }\n \n-struct tree *write_tree_from_memory(struct merge_options *opt)\n-{\n-\tstruct tree *result = NULL;\n-\tstruct index_state *istate = opt->repo->index;\n-\n-\tif (unmerged_index(istate)) {\n-\t\tint i;\n-\t\tfprintf(stderr, \"BUG: There are unmerged index entries:\\n\");\n-\t\tfor (i = 0; i < istate->cache_nr; i++) {\n-\t\t\tconst struct cache_entry *ce = istate->cache[i];\n-\t\t\tif (ce_stage(ce))\n-\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n-\t\t\t\t\t(int)ce_namelen(ce), ce->name);\n-\t\t}\n-\t\tBUG(\"unmerged index entries in merge-recursive.c\");\n-\t}\n-\n-\tif (!istate->cache_tree)\n-\t\tistate->cache_tree = cache_tree();\n-\n-\tif (!cache_tree_fully_valid(istate->cache_tree) &&\n-\t    cache_tree_update(istate, 0) < 0) {\n-\t\terr(opt, _(\"error building trees\"));\n-\t\treturn NULL;\n-\t}\n-\n-\tresult = lookup_tree(opt->repo, &istate->cache_tree->oid);\n-\n-\treturn result;\n-}\n-\n static int save_files_dirs(const struct object_id *oid,\n \t\t\t   struct strbuf *base, const char *path,\n \t\t\t   unsigned int mode, int stage, void *context)\n@@ -3472,7 +3441,8 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth && !(*result = write_tree_from_memory(opt)))\n+\tif (opt->call_depth &&\n+\t    !(*result = write_in_core_index_as_tree(opt->repo)))\n \t\treturn -1;\n \n \treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 18012fff9d..0a3033bdb0 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -113,7 +113,6 @@ int merge_recursive_generic(struct merge_options *o,\n \n void init_merge_options(struct merge_options *o,\n \t\t\tstruct repository *repo);\n-struct tree *write_tree_from_memory(struct merge_options *o);\n \n int parse_merge_opt(struct merge_options *out, const char *s);\n \n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380598","messageId":"20190817184144.32179-15-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 14/24] merge-recursive: use common name for ancestors/common/base_list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:34Z","receivedAt":"2019-08-17T18:42:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_trees(), merge_recursive(), and merge_recursive_generic() in\ntheir function headers used four different names for the merge base or\nlist of merge bases they were passed:\n  * 'common'\n  * 'ancestors'\n  * 'ca'\n  * 'base_list'\nThey were able to refer to it four different ways instead of only three\nby using a different name in the signature for the .c file than the .h\nfile.  Change all of these to 'merge_base' or 'merge_bases'.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 72 ++++++++++++++++++++++++-----------------------\n merge-recursive.h | 14 ++++-----\n 2 files changed, 44 insertions(+), 42 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 720678c21d..32871132a6 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3357,24 +3357,26 @@ static int process_entry(struct merge_options *opt,\n static int merge_trees_internal(struct merge_options *opt,\n \t\t\t\tstruct tree *head,\n \t\t\t\tstruct tree *merge,\n-\t\t\t\tstruct tree *common,\n+\t\t\t\tstruct tree *merge_base,\n \t\t\t\tstruct tree **result)\n {\n \tstruct index_state *istate = opt->repo->index;\n \tint code, clean;\n \n \tif (opt->subtree_shift) {\n-\t\tmerge = shift_tree_object(opt->repo, head, merge, opt->subtree_shift);\n-\t\tcommon = shift_tree_object(opt->repo, head, common, opt->subtree_shift);\n+\t\tmerge = shift_tree_object(opt->repo, head, merge,\n+\t\t\t\t\t  opt->subtree_shift);\n+\t\tmerge_base = shift_tree_object(opt->repo, head, merge_base,\n+\t\t\t\t\t       opt->subtree_shift);\n \t}\n \n-\tif (oid_eq(&common->object.oid, &merge->object.oid)) {\n+\tif (oid_eq(&merge_base->object.oid, &merge->object.oid)) {\n \t\toutput(opt, 0, _(\"Already up to date!\"));\n \t\t*result = head;\n \t\treturn 1;\n \t}\n \n-\tcode = unpack_trees_start(opt, common, head, merge);\n+\tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(opt, 4) || opt->call_depth)\n@@ -3402,7 +3404,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tget_files_dirs(opt, merge);\n \n \t\tentries = get_unmerged(opt->repo->index);\n-\t\tclean = detect_and_process_renames(opt, common, head, merge,\n+\t\tclean = detect_and_process_renames(opt, merge_base, head, merge,\n \t\t\t\t\t\t   entries, &re_info);\n \t\trecord_df_conflict_files(opt, entries);\n \t\tif (clean < 0)\n@@ -3470,11 +3472,11 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)\n static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t    struct commit *h1,\n \t\t\t\t    struct commit *h2,\n-\t\t\t\t    struct commit_list *ca,\n+\t\t\t\t    struct commit_list *merge_bases,\n \t\t\t\t    struct commit **result)\n {\n \tstruct commit_list *iter;\n-\tstruct commit *merged_common_ancestors;\n+\tstruct commit *merged_merge_bases;\n \tstruct tree *mrtree;\n \tint clean;\n \tconst char *ancestor_name;\n@@ -3486,39 +3488,39 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\toutput_commit_title(opt, h2);\n \t}\n \n-\tif (!ca) {\n-\t\tca = get_merge_bases(h1, h2);\n-\t\tca = reverse_commit_list(ca);\n+\tif (!merge_bases) {\n+\t\tmerge_bases = get_merge_bases(h1, h2);\n+\t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \n \tif (show(opt, 5)) {\n-\t\tunsigned cnt = commit_list_count(ca);\n+\t\tunsigned cnt = commit_list_count(merge_bases);\n \n \t\toutput(opt, 5, Q_(\"found %u common ancestor:\",\n \t\t\t\t\"found %u common ancestors:\", cnt), cnt);\n-\t\tfor (iter = ca; iter; iter = iter->next)\n+\t\tfor (iter = merge_bases; iter; iter = iter->next)\n \t\t\toutput_commit_title(opt, iter->item);\n \t}\n \n-\tmerged_common_ancestors = pop_commit(&ca);\n-\tif (merged_common_ancestors == NULL) {\n+\tmerged_merge_bases = pop_commit(&merge_bases);\n+\tif (merged_merge_bases == NULL) {\n \t\t/* if there is no common ancestor, use an empty tree */\n \t\tstruct tree *tree;\n \n \t\ttree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);\n-\t\tmerged_common_ancestors = make_virtual_commit(opt->repo,\n-\t\t\t\t\t\t\t      tree, \"ancestor\");\n+\t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n+\t\t\t\t\t\t\t \"ancestor\");\n \t\tancestor_name = \"empty tree\";\n-\t} else if (ca) {\n+\t} else if (merge_bases) {\n \t\tancestor_name = \"merged common ancestors\";\n \t} else {\n \t\tstrbuf_add_unique_abbrev(&merge_base_abbrev,\n-\t\t\t\t\t &merged_common_ancestors->object.oid,\n+\t\t\t\t\t &merged_merge_bases->object.oid,\n \t\t\t\t\t DEFAULT_ABBREV);\n \t\tancestor_name = merge_base_abbrev.buf;\n \t}\n \n-\tfor (iter = ca; iter; iter = iter->next) {\n+\tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n \t\topt->call_depth++;\n \t\t/*\n@@ -3534,14 +3536,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tsaved_b2 = opt->branch2;\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n-\t\tif (merge_recursive_internal(opt, merged_common_ancestors, iter->item,\n-\t\t\t\t    NULL, &merged_common_ancestors) < 0)\n+\t\tif (merge_recursive_internal(opt, merged_merge_bases, iter->item,\n+\t\t\t\t\t     NULL, &merged_merge_bases) < 0)\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->call_depth--;\n \n-\t\tif (!merged_common_ancestors)\n+\t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n@@ -3554,7 +3556,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t\t  merged_common_ancestors),\n+\t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &mrtree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n@@ -3597,7 +3599,7 @@ static void merge_finalize(struct merge_options *opt)\n int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common)\n+\t\tstruct tree *merge_base)\n {\n \tint clean;\n \tstruct tree *ignored;\n@@ -3606,7 +3608,7 @@ int merge_trees(struct merge_options *opt,\n \n \tif (merge_start(opt, head))\n \t\treturn -1;\n-\tclean = merge_trees_internal(opt, head, merge, common, &ignored);\n+\tclean = merge_trees_internal(opt, head, merge, merge_base, &ignored);\n \tmerge_finalize(opt);\n \n \treturn clean;\n@@ -3615,7 +3617,7 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ca,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result)\n {\n \tint clean;\n@@ -3624,7 +3626,7 @@ int merge_recursive(struct merge_options *opt,\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n-\tclean = merge_recursive_internal(opt, h1, h2, ca, result);\n+\tclean = merge_recursive_internal(opt, h1, h2, merge_bases, result);\n \tmerge_finalize(opt);\n \n \treturn clean;\n@@ -3652,8 +3654,8 @@ static struct commit *get_ref(struct repository *repo,\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_base_list,\n-\t\t\t    const struct object_id **base_list,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result)\n {\n \tint clean;\n@@ -3662,14 +3664,14 @@ int merge_recursive_generic(struct merge_options *opt,\n \tstruct commit *next_commit = get_ref(opt->repo, merge, opt->branch2);\n \tstruct commit_list *ca = NULL;\n \n-\tif (base_list) {\n+\tif (merge_bases) {\n \t\tint i;\n-\t\tfor (i = 0; i < num_base_list; ++i) {\n+\t\tfor (i = 0; i < num_merge_bases; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, base_list[i],\n-\t\t\t\t\t     oid_to_hex(base_list[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, merge_bases[i],\n+\t\t\t\t\t     oid_to_hex(merge_bases[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n-\t\t\t\t\t   oid_to_hex(base_list[i]));\n+\t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t}\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 0a3033bdb0..6f351098a5 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -81,14 +81,14 @@ static inline int merge_detect_rename(struct merge_options *o)\n  *\n  * NOTE: empirically, about a decade ago it was determined that with more\n  *       than two merge bases, optimal behavior was found when the\n- *       ancestors were passed in the order of oldest merge base to newest\n- *       one.  Also, ancestors will be consumed (emptied) so make a copy if\n- *       you need it.\n+ *       merge_bases were passed in the order of oldest commit to newest\n+ *       commit.  Also, merge_bases will be consumed (emptied) so make a\n+ *       copy if you need it.\n  */\n int merge_recursive(struct merge_options *o,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *ancestors,\n+\t\t    struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /*\n@@ -98,7 +98,7 @@ int merge_recursive(struct merge_options *o,\n int merge_trees(struct merge_options *o,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n-\t\tstruct tree *common);\n+\t\tstruct tree *merge_base);\n \n /*\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n@@ -107,8 +107,8 @@ int merge_trees(struct merge_options *o,\n int merge_recursive_generic(struct merge_options *o,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n-\t\t\t    int num_ca,\n-\t\t\t    const struct object_id **ca,\n+\t\t\t    int num_merge_bases,\n+\t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n void init_merge_options(struct merge_options *o,\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380600","messageId":"20190817184144.32179-16-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 15/24] merge-recursive: rename 'mrtree' to 'result_tree', for clarity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:35Z","receivedAt":"2019-08-17T18:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"It is not at all clear what 'mr' was supposed to stand for, at least not\nto me.  Pick a clearer name for this variable.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 32871132a6..1823a87706 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3477,7 +3477,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n {\n \tstruct commit_list *iter;\n \tstruct commit *merged_merge_bases;\n-\tstruct tree *mrtree;\n+\tstruct tree *result_tree;\n \tint clean;\n \tconst char *ancestor_name;\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n@@ -3557,7 +3557,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n \t\t\t\t     repo_get_commit_tree(opt->repo,\n \t\t\t\t\t\t\t  merged_merge_bases),\n-\t\t\t\t     &mrtree);\n+\t\t\t\t     &result_tree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n \t\tflush_output(opt);\n@@ -3565,7 +3565,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t}\n \n \tif (opt->call_depth) {\n-\t\t*result = make_virtual_commit(opt->repo, mrtree, \"merged tree\");\n+\t\t*result = make_virtual_commit(opt->repo, result_tree,\n+\t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380601","messageId":"20190817184144.32179-18-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 17/24] merge-recursive: move some definitions around to clean up the header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:37Z","receivedAt":"2019-08-17T18:42:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"No substantive code changes (view this with diff --color-moved), but\na few small code cleanups:\n  * Move structs and an inline function only used by merge-recursive.c\n    into merge-recursive.c\n  * Re-order function declarations to be more logical\n  * Add or fix some explanatory comments\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 31 +++++++++++++++++\n merge-recursive.h | 87 +++++++++++++++++++++++------------------------\n 2 files changed, 73 insertions(+), 45 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1823a87706..9807b24c65 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -54,6 +54,24 @@ static unsigned int path_hash(const char *path)\n \treturn ignore_case ? strihash(path) : strhash(path);\n }\n \n+/*\n+ * For dir_rename_entry, directory names are stored as a full path from the\n+ * toplevel of the repository and do not include a trailing '/'.  Also:\n+ *\n+ *   dir:                original name of directory being renamed\n+ *   non_unique_new_dir: if true, could not determine new_dir\n+ *   new_dir:            final name of directory being renamed\n+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n+ *                       in get_directory_renames() for details\n+ */\n+struct dir_rename_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *dir;\n+\tunsigned non_unique_new_dir:1;\n+\tstruct strbuf new_dir;\n+\tstruct string_list possible_new_dirs;\n+};\n+\n static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t      char *dir)\n {\n@@ -92,6 +110,13 @@ static void dir_rename_entry_init(struct dir_rename_entry *entry,\n \tstring_list_init(&entry->possible_new_dirs, 0);\n }\n \n+struct collision_entry {\n+\tstruct hashmap_entry ent; /* must be the first member! */\n+\tchar *target_file;\n+\tstruct string_list source_files;\n+\tunsigned reported_already:1;\n+};\n+\n static struct collision_entry *collision_find_entry(struct hashmap *hashmap,\n \t\t\t\t\t\t    char *target_file)\n {\n@@ -358,6 +383,12 @@ static int add_cacheinfo(struct merge_options *opt,\n \treturn ret;\n }\n \n+static inline int merge_detect_rename(struct merge_options *opt)\n+{\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+}\n+\n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n {\n \tparse_tree(tree);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 2cb3844ad9..0fdae904dd 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -43,47 +43,50 @@ struct merge_options {\n \tstruct repository *repo;\n };\n \n+void init_merge_options(struct merge_options *opt, struct repository *repo);\n+\n+/* parse the option in s and update the relevant field of opt */\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n+\n /*\n- * For dir_rename_entry, directory names are stored as a full path from the\n- * toplevel of the repository and do not include a trailing '/'.  Also:\n- *\n- *   dir:                original name of directory being renamed\n- *   non_unique_new_dir: if true, could not determine new_dir\n- *   new_dir:            final name of directory being renamed\n- *   possible_new_dirs:  temporary used to help determine new_dir; see comments\n- *                       in get_directory_renames() for details\n+ * RETURN VALUES: All the merge_* functions below return a value as follows:\n+ *   > 0     Merge was clean\n+ *   = 0     Merge had conflicts\n+ *   < 0     Merge hit an unexpected and unrecoverable problem (e.g. disk\n+ *             full) and aborted merge part-way through.\n  */\n-struct dir_rename_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *dir;\n-\tunsigned non_unique_new_dir:1;\n-\tstruct strbuf new_dir;\n-\tstruct string_list possible_new_dirs;\n-};\n-\n-struct collision_entry {\n-\tstruct hashmap_entry ent; /* must be the first member! */\n-\tchar *target_file;\n-\tstruct string_list source_files;\n-\tunsigned reported_already:1;\n-};\n \n-static inline int merge_detect_rename(struct merge_options *opt)\n-{\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n-}\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - No commit is created\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is not updated\n+ *   - The working tree is updated with results of the merge\n+ */\n+int merge_trees(struct merge_options *opt,\n+\t\tstruct tree *head,\n+\t\tstruct tree *merge,\n+\t\tstruct tree *merge_base);\n \n /*\n  * merge_recursive is like merge_trees() but with recursive ancestor\n- * consolidation, and when successful, it creates an actual commit\n- * and writes its address to *result.\n+ * consolidation and, if the commit is clean, creation of a commit.\n  *\n  * NOTE: empirically, about a decade ago it was determined that with more\n  *       than two merge bases, optimal behavior was found when the\n  *       merge_bases were passed in the order of oldest commit to newest\n  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n  *       copy if you need it.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - If merge is clean, a commit is created and its address written to *result\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is not updated\n+ *   - The working tree is updated with results of the merge\n  */\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n@@ -92,17 +95,16 @@ int merge_recursive(struct merge_options *opt,\n \t\t    struct commit **result);\n \n /*\n- * rename-detecting three-way merge, no recursion; result of merge is written\n- * to opt->repo->index.\n- */\n-int merge_trees(struct merge_options *opt,\n-\t\tstruct tree *head,\n-\t\tstruct tree *merge,\n-\t\tstruct tree *merge_base);\n-\n-/*\n- * \"git-merge-recursive\" can be fed trees; wrap them into\n- * virtual commits and call merge_recursive() proper.\n+ * merge_recursive_generic can operate on trees instead of commits, by\n+ * wrapping the trees into virtual commits, and calling merge_recursive().\n+ * It also writes out the in-memory index to disk if the merge is successful.\n+ *\n+ * Outputs:\n+ *   - See RETURN VALUES above\n+ *   - If merge is clean, a commit is created and its address written to *result\n+ *   - opt->repo->index has the new index\n+ *   - $GIT_INDEX_FILE is updated\n+ *   - The working tree is updated with results of the merge\n  */\n int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n@@ -111,9 +113,4 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *opt,\n-\t\t\tstruct repository *repo);\n-\n-int parse_merge_opt(struct merge_options *opt, const char *s);\n-\n #endif\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380602","messageId":"20190817184144.32179-8-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 07/24] merge-recursive: remove another implicit dependency on the_repository","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:27Z","receivedAt":"2019-08-17T18:42:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Commit d7cf3a96e9a0 (\"merge-recursive.c: remove implicit dependency on\nthe_repository\", 2019-01-12) and follow-ups like commit 34e7771bc644\n(\"Use the right 'struct repository' instead of the_repository\",\n2019-06-27), removed most implicit uses of the_repository.  Convert\ncalls to get_commit_tree() to instead use repo_get_commit_tree() to get\nrid of another.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 7 +++++--\n 1 file changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1d4df952e5..88a33e6e72 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3587,8 +3587,11 @@ int merge_recursive(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = ancestor_name;\n-\tclean = merge_trees(opt, get_commit_tree(h1), get_commit_tree(h2),\n-\t\t\t    get_commit_tree(merged_common_ancestors),\n+\tclean = merge_trees(opt,\n+\t\t\t    repo_get_commit_tree(opt->repo, h1),\n+\t\t\t    repo_get_commit_tree(opt->repo, h2),\n+\t\t\t    repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t merged_common_ancestors),\n \t\t\t    &mrtree);\n \tstrbuf_release(&merge_base_abbrev);\n \tif (clean < 0) {\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380603","messageId":"20190817184144.32179-17-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 16/24] merge-recursive: rename merge_options argument to opt in header","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:36Z","receivedAt":"2019-08-17T18:42:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 259ccb6cc324 (\"merge-recursive: rename merge_options argument\nfrom 'o' to 'opt'\", 2019-04-05), I renamed a bunch of function\narguments in merge-recursive.c, but forgot to make that same change to\nmerge-recursive.h.  Make the two match.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.h | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 6f351098a5..2cb3844ad9 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -68,10 +68,10 @@ struct collision_entry {\n \tunsigned reported_already:1;\n };\n \n-static inline int merge_detect_rename(struct merge_options *o)\n+static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn o->merge_detect_rename >= 0 ? o->merge_detect_rename :\n-\t\to->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;\n+\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n+\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n }\n \n /*\n@@ -85,7 +85,7 @@ static inline int merge_detect_rename(struct merge_options *o)\n  *       commit.  Also, merge_bases will be consumed (emptied) so make a\n  *       copy if you need it.\n  */\n-int merge_recursive(struct merge_options *o,\n+int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n \t\t    struct commit_list *merge_bases,\n@@ -95,7 +95,7 @@ int merge_recursive(struct merge_options *o,\n  * rename-detecting three-way merge, no recursion; result of merge is written\n  * to opt->repo->index.\n  */\n-int merge_trees(struct merge_options *o,\n+int merge_trees(struct merge_options *opt,\n \t\tstruct tree *head,\n \t\tstruct tree *merge,\n \t\tstruct tree *merge_base);\n@@ -104,16 +104,16 @@ int merge_trees(struct merge_options *o,\n  * \"git-merge-recursive\" can be fed trees; wrap them into\n  * virtual commits and call merge_recursive() proper.\n  */\n-int merge_recursive_generic(struct merge_options *o,\n+int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n \t\t\t    const struct object_id **merge_bases,\n \t\t\t    struct commit **result);\n \n-void init_merge_options(struct merge_options *o,\n+void init_merge_options(struct merge_options *opt,\n \t\t\tstruct repository *repo);\n \n-int parse_merge_opt(struct merge_options *out, const char *s);\n+int parse_merge_opt(struct merge_options *opt, const char *s);\n \n #endif\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380605","messageId":"20190817184144.32179-21-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 20/24] merge-recursive: avoid losing output and leaking memory holding that output","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:40Z","receivedAt":"2019-08-17T18:42:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If opt->buffer_output is less than 2, then merge_trees(),\nmerge_recursive(), and merge_recursive_generic() are all supposed to\nflush the opt->obuf output buffer to stdout and release any memory it\nholds.  merge_trees() did not do this.  Move the logic that handles this\nfor merge_recursive_internal() to merge_finalize() so that all three\nmethods handle this requirement.\n\nNote that this bug didn't cause any problems currently, because there\nare only two callers of merge_trees() right now (a git grep for\n'merge_trees(' is misleading because builtin/merge-tree.c also defines a\n'merge_tree' function that is unrelated), and only one of those is\ncalled with buffer_output less than 2 (builtin/checkout.c), but it set\nopt->verbosity to 0, for which there is only currently one non-error\nmessage that would be shown: \"Already up to date!\".  However, that one\nmessage can only occur when the merge is utterly trivial (the merge base\ntree exactly matches the merge tree), and builtin/checkout.c already\nattempts a trivial merge via unpack_trees() before falling back to\nmerge_trees().\n\nAlso, if opt->buffer_output is 2, then the caller is responsible to\nhandle showing any output in opt->obuf and for free'ing it.  This\nrequirement might be easy to overlook, so add a comment to\nmerge-recursive.h pointing it out.  (There are currently two callers\nthat set buffer_output to 2, both in sequencer.c, and both of which\nhandle this correctly.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 6 +++---\n merge-recursive.h | 3 ++-\n 2 files changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 43dec3307e..262db8bebb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3598,9 +3598,6 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-\tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n-\t\tstrbuf_release(&opt->obuf);\n \treturn clean;\n }\n \n@@ -3620,6 +3617,9 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \n static void merge_finalize(struct merge_options *opt)\n {\n+\tflush_output(opt);\n+\tif (!opt->call_depth && opt->buffer_output < 2)\n+\t\tstrbuf_release(&opt->obuf);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opt->needed_rename_limit, 0);\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 9e040608fe..933d6e7642 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -38,7 +38,8 @@ struct merge_options {\n \t/* console output related options */\n \tint verbosity;\n \tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n-\tstruct strbuf obuf;     /* output buffer */\n+\tstruct strbuf obuf;     /* output buffer; if buffer_output == 2, caller\n+\t\t\t\t * must handle and call strbuf_release */\n \n \t/* miscellaneous control options */\n \tconst char *subtree_shift;\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380606","messageId":"20190817184144.32179-22-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 21/24] merge-recursive: split internal fields into a separate struct","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:41Z","receivedAt":"2019-08-17T18:42:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_options has several internal fields that should not be set or read\nby external callers.  This just complicates the API.  Move them into an\nopaque merge_options_internal struct that is defined only in\nmerge-recursive.c and keep these out of merge-recursive.h.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 185 ++++++++++++++++++++++++----------------------\n merge-recursive.h |  17 ++---\n 2 files changed, 105 insertions(+), 97 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 262db8bebb..c92993e8f0 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -29,6 +29,15 @@\n #include \"revision.h\"\n #include \"commit-reach.h\"\n \n+struct merge_options_internal {\n+\tint call_depth;\n+\tint needed_rename_limit;\n+\tstruct hashmap current_file_dir_set;\n+\tstruct string_list df_conflict_file_set;\n+\tstruct unpack_trees_options unpack_opts;\n+\tstruct index_state orig_index;\n+};\n+\n struct path_hashmap_entry {\n \tstruct hashmap_entry e;\n \tchar path[FLEX_ARRAY];\n@@ -309,7 +318,8 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \n static int show(struct merge_options *opt, int v)\n {\n-\treturn (!opt->call_depth && opt->verbosity >= v) || opt->verbosity >= 5;\n+\treturn (!opt->priv->call_depth && opt->verbosity >= v) ||\n+\t\topt->verbosity >= 5;\n }\n \n __attribute__((format (printf, 3, 4)))\n@@ -320,7 +330,7 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)\n \tif (!show(opt, v))\n \t\treturn;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \n \tva_start(ap, fmt);\n \tstrbuf_vaddf(&opt->obuf, fmt, ap);\n@@ -335,7 +345,7 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit\n {\n \tstruct merge_remote_desc *desc;\n \n-\tstrbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);\n+\tstrbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);\n \tdesc = merge_remote_util(commit);\n \tif (desc)\n \t\tstrbuf_addf(&opt->obuf, \"virtual %s\\n\", desc->name);\n@@ -403,43 +413,43 @@ static int unpack_trees_start(struct merge_options *opt,\n \tstruct tree_desc t[3];\n \tstruct index_state tmp_index = { NULL };\n \n-\tmemset(&opt->unpack_opts, 0, sizeof(opt->unpack_opts));\n-\tif (opt->call_depth)\n-\t\topt->unpack_opts.index_only = 1;\n+\tmemset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));\n+\tif (opt->priv->call_depth)\n+\t\topt->priv->unpack_opts.index_only = 1;\n \telse\n-\t\topt->unpack_opts.update = 1;\n-\topt->unpack_opts.merge = 1;\n-\topt->unpack_opts.head_idx = 2;\n-\topt->unpack_opts.fn = threeway_merge;\n-\topt->unpack_opts.src_index = opt->repo->index;\n-\topt->unpack_opts.dst_index = &tmp_index;\n-\topt->unpack_opts.aggressive = !merge_detect_rename(opt);\n-\tsetup_unpack_trees_porcelain(&opt->unpack_opts, \"merge\");\n+\t\topt->priv->unpack_opts.update = 1;\n+\topt->priv->unpack_opts.merge = 1;\n+\topt->priv->unpack_opts.head_idx = 2;\n+\topt->priv->unpack_opts.fn = threeway_merge;\n+\topt->priv->unpack_opts.src_index = opt->repo->index;\n+\topt->priv->unpack_opts.dst_index = &tmp_index;\n+\topt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);\n+\tsetup_unpack_trees_porcelain(&opt->priv->unpack_opts, \"merge\");\n \n \tinit_tree_desc_from_tree(t+0, common);\n \tinit_tree_desc_from_tree(t+1, head);\n \tinit_tree_desc_from_tree(t+2, merge);\n \n-\trc = unpack_trees(3, t, &opt->unpack_opts);\n+\trc = unpack_trees(3, t, &opt->priv->unpack_opts);\n \tcache_tree_free(&opt->repo->index->cache_tree);\n \n \t/*\n-\t * Update opt->repo->index to match the new results, AFTER saving a copy\n-\t * in opt->orig_index.  Update src_index to point to the saved copy.\n-\t * (verify_uptodate() checks src_index, and the original index is\n-\t * the one that had the necessary modification timestamps.)\n+\t * Update opt->repo->index to match the new results, AFTER saving a\n+\t * copy in opt->priv->orig_index.  Update src_index to point to the\n+\t * saved copy.  (verify_uptodate() checks src_index, and the original\n+\t * index is the one that had the necessary modification timestamps.)\n \t */\n-\topt->orig_index = *opt->repo->index;\n+\topt->priv->orig_index = *opt->repo->index;\n \t*opt->repo->index = tmp_index;\n-\topt->unpack_opts.src_index = &opt->orig_index;\n+\topt->priv->unpack_opts.src_index = &opt->priv->orig_index;\n \n \treturn rc;\n }\n \n static void unpack_trees_finish(struct merge_options *opt)\n {\n-\tdiscard_index(&opt->orig_index);\n-\tclear_unpack_trees_porcelain(&opt->unpack_opts);\n+\tdiscard_index(&opt->priv->orig_index);\n+\tclear_unpack_trees_porcelain(&opt->priv->unpack_opts);\n }\n \n static int save_files_dirs(const struct object_id *oid,\n@@ -454,7 +464,7 @@ static int save_files_dirs(const struct object_id *oid,\n \n \tFLEX_ALLOC_MEM(entry, path, base->buf, base->len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \n \tstrbuf_setlen(base, baselen);\n \treturn (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);\n@@ -585,7 +595,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t * If we're merging merge-bases, we don't want to bother with\n \t * any working directory changes.\n \t */\n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn;\n \n \t/* Ensure D/F conflicts are adjacent in the entries list. */\n@@ -597,7 +607,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \tdf_sorted_entries.cmp = string_list_df_name_compare;\n \tstring_list_sort(&df_sorted_entries);\n \n-\tstring_list_clear(&opt->df_conflict_file_set, 1);\n+\tstring_list_clear(&opt->priv->df_conflict_file_set, 1);\n \tfor (i = 0; i < df_sorted_entries.nr; i++) {\n \t\tconst char *path = df_sorted_entries.items[i].string;\n \t\tint len = strlen(path);\n@@ -613,7 +623,7 @@ static void record_df_conflict_files(struct merge_options *opt,\n \t\t    len > last_len &&\n \t\t    memcmp(path, last_file, last_len) == 0 &&\n \t\t    path[last_len] == '/') {\n-\t\t\tstring_list_insert(&opt->df_conflict_file_set, last_file);\n+\t\t\tstring_list_insert(&opt->priv->df_conflict_file_set, last_file);\n \t\t}\n \n \t\t/*\n@@ -680,8 +690,8 @@ static void update_entry(struct stage_data *entry,\n static int remove_file(struct merge_options *opt, int clean,\n \t\t       const char *path, int no_wd)\n {\n-\tint update_cache = opt->call_depth || clean;\n-\tint update_working_directory = !opt->call_depth && !no_wd;\n+\tint update_cache = opt->priv->call_depth || clean;\n+\tint update_working_directory = !opt->priv->call_depth && !no_wd;\n \n \tif (update_cache) {\n \t\tif (remove_file_from_index(opt->repo->index, path))\n@@ -724,16 +734,16 @@ static char *unique_path(struct merge_options *opt,\n \tadd_flattened_path(&newpath, branch);\n \n \tbase_len = newpath.len;\n-\twhile (hashmap_get_from_hash(&opt->current_file_dir_set,\n+\twhile (hashmap_get_from_hash(&opt->priv->current_file_dir_set,\n \t\t\t\t     path_hash(newpath.buf), newpath.buf) ||\n-\t       (!opt->call_depth && file_exists(newpath.buf))) {\n+\t       (!opt->priv->call_depth && file_exists(newpath.buf))) {\n \t\tstrbuf_setlen(&newpath, base_len);\n \t\tstrbuf_addf(&newpath, \"_%d\", suffix++);\n \t}\n \n \tFLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);\n \thashmap_entry_init(entry, path_hash(entry->path));\n-\thashmap_add(&opt->current_file_dir_set, entry);\n+\thashmap_add(&opt->priv->current_file_dir_set, entry);\n \treturn strbuf_detach(&newpath, NULL);\n }\n \n@@ -775,7 +785,7 @@ static int dir_in_way(struct index_state *istate, const char *path,\n static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\t\t\t   const struct diff_filespec *blob)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \tstruct cache_entry *ce;\n \n \tif (0 > pos)\n@@ -783,7 +793,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n \t\treturn 0;\n \n \t/* See if the file we were tracking before matches */\n-\tce = opt->orig_index.cache[pos];\n+\tce = opt->priv->orig_index.cache[pos];\n \treturn (oid_eq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);\n }\n \n@@ -792,7 +802,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,\n  */\n static int was_tracked(struct merge_options *opt, const char *path)\n {\n-\tint pos = index_name_pos(&opt->orig_index, path, strlen(path));\n+\tint pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n \n \tif (0 <= pos)\n \t\t/* we were tracking this path before the merge */\n@@ -849,12 +859,12 @@ static int was_dirty(struct merge_options *opt, const char *path)\n \tstruct cache_entry *ce;\n \tint dirty = 1;\n \n-\tif (opt->call_depth || !was_tracked(opt, path))\n+\tif (opt->priv->call_depth || !was_tracked(opt, path))\n \t\treturn !dirty;\n \n-\tce = index_file_exists(opt->unpack_opts.src_index,\n+\tce = index_file_exists(opt->priv->unpack_opts.src_index,\n \t\t\t       path, strlen(path), ignore_case);\n-\tdirty = verify_uptodate(ce, &opt->unpack_opts) != 0;\n+\tdirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;\n \treturn dirty;\n }\n \n@@ -864,8 +874,8 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \tconst char *msg = _(\"failed to create path '%s'%s\");\n \n \t/* Unlink any D/F conflict files that are in the way */\n-\tfor (i = 0; i < opt->df_conflict_file_set.nr; i++) {\n-\t\tconst char *df_path = opt->df_conflict_file_set.items[i].string;\n+\tfor (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {\n+\t\tconst char *df_path = opt->priv->df_conflict_file_set.items[i].string;\n \t\tsize_t pathlen = strlen(path);\n \t\tsize_t df_pathlen = strlen(df_path);\n \t\tif (df_pathlen < pathlen &&\n@@ -875,7 +885,7 @@ static int make_room_for_path(struct merge_options *opt, const char *path)\n \t\t\t       _(\"Removing %s to make room for subdirectory\\n\"),\n \t\t\t       df_path);\n \t\t\tunlink(df_path);\n-\t\t\tunsorted_string_list_delete_item(&opt->df_conflict_file_set,\n+\t\t\tunsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,\n \t\t\t\t\t\t\t i, 0);\n \t\t\tbreak;\n \t\t}\n@@ -916,7 +926,7 @@ static int update_file_flags(struct merge_options *opt,\n {\n \tint ret = 0;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\tupdate_wd = 0;\n \n \tif (update_wd) {\n@@ -1001,7 +1011,7 @@ static int update_file(struct merge_options *opt,\n \t\t       const char *path)\n {\n \treturn update_file_flags(opt, contents, path,\n-\t\t\t\t opt->call_depth || clean, !opt->call_depth);\n+\t\t\t\t opt->priv->call_depth || clean, !opt->priv->call_depth);\n }\n \n /* Low level file merging, update and removal */\n@@ -1030,7 +1040,7 @@ static int merge_3way(struct merge_options *opt,\n \tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = opt->xdl_opts;\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\tll_opts.virtual_ancestor = 1;\n \t\tll_opts.variant = 0;\n \t} else {\n@@ -1161,7 +1171,7 @@ static int merge_submodule(struct merge_options *opt,\n \tstruct object_array merges;\n \n \tint i;\n-\tint search = !opt->call_depth;\n+\tint search = !opt->priv->call_depth;\n \n \t/* store a in result in case we fail */\n \toidcpy(result, a);\n@@ -1383,7 +1393,7 @@ static int handle_rename_via_dir(struct merge_options *opt,\n \t\t\t       MERGE_DIRECTORY_RENAMES_CONFLICT);\n \tassert(ren->dir_rename_original_dest);\n \n-\tif (!opt->call_depth && would_lose_untracked(opt, dest->path)) {\n+\tif (!opt->priv->call_depth && would_lose_untracked(opt, dest->path)) {\n \t\tmark_conflicted = 1;\n \t\tfile_path = unique_path(opt, dest->path, ren->branch);\n \t\toutput(opt, 1, _(\"Error: Refusing to lose untracked file at %s; \"\n@@ -1426,12 +1436,12 @@ static int handle_change_delete(struct merge_options *opt,\n \tconst char *update_path = path;\n \tint ret = 0;\n \n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0) ||\n-\t    (!opt->call_depth && would_lose_untracked(opt, path))) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0) ||\n+\t    (!opt->priv->call_depth && would_lose_untracked(opt, path))) {\n \t\tupdate_path = alt_path = unique_path(opt, path, change_branch);\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * We cannot arbitrarily accept either a_sha or b_sha as\n \t\t * correct; since there is no true \"middle point\" between\n@@ -1506,14 +1516,14 @@ static int handle_rename_delete(struct merge_options *opt,\n \t\t\t\t     opt->branch2 : opt->branch1);\n \n \tif (handle_change_delete(opt,\n-\t\t\t\t opt->call_depth ? orig->path : dest->path,\n-\t\t\t\t opt->call_depth ? NULL : orig->path,\n+\t\t\t\t opt->priv->call_depth ? orig->path : dest->path,\n+\t\t\t\t opt->priv->call_depth ? NULL : orig->path,\n \t\t\t\t orig, dest,\n \t\t\t\t rename_branch, delete_branch,\n \t\t\t\t _(\"rename\"), _(\"renamed\")))\n \t\treturn -1;\n \n-\tif (opt->call_depth)\n+\tif (opt->priv->call_depth)\n \t\treturn remove_file_from_index(opt->repo->index, dest->path);\n \telse\n \t\treturn update_stages(opt, dest->path, NULL,\n@@ -1550,7 +1560,7 @@ static int handle_file_collision(struct merge_options *opt,\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n-\tif (opt->call_depth && (prev_path1 || prev_path2)) {\n+\tif (opt->priv->call_depth && (prev_path1 || prev_path2)) {\n \t\t/* Put first file (a->oid, a->mode) in its original spot */\n \t\tif (prev_path1) {\n \t\t\tif (update_file(opt, 1, a, prev_path1))\n@@ -1579,10 +1589,10 @@ static int handle_file_collision(struct merge_options *opt,\n \t/* Remove rename sources if rename/add or rename/rename(2to1) */\n \tif (prev_path1)\n \t\tremove_file(opt, 1, prev_path1,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path1));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path1));\n \tif (prev_path2)\n \t\tremove_file(opt, 1, prev_path2,\n-\t\t\t    opt->call_depth || would_lose_untracked(opt, prev_path2));\n+\t\t\t    opt->priv->call_depth || would_lose_untracked(opt, prev_path2));\n \n \t/*\n \t * Remove the collision path, if it wouldn't cause dirty contents\n@@ -1624,12 +1634,12 @@ static int handle_file_collision(struct merge_options *opt,\n \tnull.mode = 0;\n \n \tif (merge_mode_and_contents(opt, &null, a, b, collide_path,\n-\t\t\t\t    branch1, branch2, opt->call_depth * 2, &mfi))\n+\t\t\t\t    branch1, branch2, opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tmfi.clean &= !alt_path;\n \tif (update_file(opt, mfi.clean, &mfi.blob, update_path))\n \t\treturn -1;\n-\tif (!mfi.clean && !opt->call_depth &&\n+\tif (!mfi.clean && !opt->priv->call_depth &&\n \t    update_stages(opt, collide_path, NULL, a, b))\n \t\treturn -1;\n \tfree(alt_path);\n@@ -1669,7 +1679,7 @@ static int handle_rename_add(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[other_stage],\n \t\t\t\t    prev_path_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(prev_path_desc);\n \n@@ -1687,7 +1697,7 @@ static char *find_path_for_conflict(struct merge_options *opt,\n \t\t\t\t    const char *branch2)\n {\n \tchar *new_path = NULL;\n-\tif (dir_in_way(opt->repo->index, path, !opt->call_depth, 0)) {\n+\tif (dir_in_way(opt->repo->index, path, !opt->priv->call_depth, 0)) {\n \t\tnew_path = unique_path(opt, path, branch1);\n \t\toutput(opt, 1, _(\"%s is a directory in %s adding \"\n \t\t\t       \"as %s instead\"),\n@@ -1718,17 +1728,17 @@ static int handle_rename_rename_1to2(struct merge_options *opt,\n \t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\"),\n \t       o->path, a->path, ci->ren1->branch,\n \t       o->path, b->path, ci->ren2->branch,\n-\t       opt->call_depth ? _(\" (left unresolved)\") : \"\");\n+\t       opt->priv->call_depth ? _(\" (left unresolved)\") : \"\");\n \n \tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n \t\t\t    a->path, b->path, o->path);\n \tif (merge_mode_and_contents(opt, o, a, b, path_desc,\n \t\t\t\t    ci->ren1->branch, ci->ren2->branch,\n-\t\t\t\t    opt->call_depth * 2, &mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, &mfi))\n \t\treturn -1;\n \tfree(path_desc);\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1843,12 +1853,12 @@ static int handle_rename_rename_2to1(struct merge_options *opt,\n \t\t\t\t    &ci->ren1->src_entry->stages[ostage1],\n \t\t\t\t    path_side_1_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(opt, b,\n \t\t\t\t    &ci->ren2->src_entry->stages[ostage2],\n \t\t\t\t    c2, path_side_2_desc,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    1 + opt->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + opt->priv->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -1889,8 +1899,8 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \tdiff_setup_done(&opts);\n \tdiff_tree_oid(&o_tree->object.oid, &tree->object.oid, \"\", &opts);\n \tdiffcore_std(&opts);\n-\tif (opts.needed_rename_limit > opt->needed_rename_limit)\n-\t\topt->needed_rename_limit = opts.needed_rename_limit;\n+\tif (opts.needed_rename_limit > opt->priv->needed_rename_limit)\n+\t\topt->priv->needed_rename_limit = opts.needed_rename_limit;\n \n \tret = xmalloc(sizeof(*ret));\n \t*ret = diff_queued_diff;\n@@ -2865,7 +2875,7 @@ static int detect_and_process_renames(struct merge_options *opt,\n \n \tif ((opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) ||\n \t    (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT &&\n-\t     !opt->call_depth)) {\n+\t     !opt->priv->call_depth)) {\n \t\tdir_re_head = get_directory_renames(head_pairs);\n \t\tdir_re_merge = get_directory_renames(merge_pairs);\n \n@@ -3022,13 +3032,13 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\treason = _(\"add/add\");\n \n \tassert(o->path && a->path && b->path);\n-\tif (ci && dir_in_way(opt->repo->index, path, !opt->call_depth,\n+\tif (ci && dir_in_way(opt->repo->index, path, !opt->priv->call_depth,\n \t\t\t     S_ISGITLINK(ci->ren1->pair->two->mode)))\n \t\tdf_conflict_remains = 1;\n \n \tif (merge_mode_and_contents(opt, o, a, b, path,\n \t\t\t\t    opt->branch1, opt->branch2,\n-\t\t\t\t    opt->call_depth * 2, mfi))\n+\t\t\t\t    opt->priv->call_depth * 2, mfi))\n \t\treturn -1;\n \n \t/*\n@@ -3044,7 +3054,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \t\toutput(opt, 3, _(\"Skipped %s (merged same as existing)\"), path);\n \t\tif (add_cacheinfo(opt, &mfi->blob, path,\n-\t\t\t\t  0, (!opt->call_depth && !is_dirty), 0))\n+\t\t\t\t  0, (!opt->priv->call_depth && !is_dirty), 0))\n \t\t\treturn -1;\n \t\t/*\n \t\t * However, add_cacheinfo() will delete the old cache entry\n@@ -3052,8 +3062,8 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \t\t * flag to avoid making the file appear as if it were\n \t\t * deleted by the user.\n \t\t */\n-\t\tpos = index_name_pos(&opt->orig_index, path, strlen(path));\n-\t\tce = opt->orig_index.cache[pos];\n+\t\tpos = index_name_pos(&opt->priv->orig_index, path, strlen(path));\n+\t\tce = opt->priv->orig_index.cache[pos];\n \t\tif (ce_skip_worktree(ce)) {\n \t\t\tpos = index_name_pos(opt->repo->index, path, strlen(path));\n \t\t\tce = opt->repo->index->cache[pos];\n@@ -3074,7 +3084,7 @@ static int handle_content_merge(struct merge_file_info *mfi,\n \n \tif (df_conflict_remains || is_dirty) {\n \t\tchar *new_path;\n-\t\tif (opt->call_depth) {\n+\t\tif (opt->priv->call_depth) {\n \t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t} else {\n \t\t\tif (!mfi->clean) {\n@@ -3333,7 +3343,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\tconf = _(\"directory/file\");\n \t\t}\n \t\tif (dir_in_way(opt->repo->index, path,\n-\t\t\t       !opt->call_depth && !S_ISGITLINK(a->mode),\n+\t\t\t       !opt->priv->call_depth && !S_ISGITLINK(a->mode),\n \t\t\t       0)) {\n \t\t\tchar *new_path = unique_path(opt, path, add_branch);\n \t\t\tclean_merge = 0;\n@@ -3342,7 +3352,7 @@ static int process_entry(struct merge_options *opt,\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tif (update_file(opt, 0, contents, new_path))\n \t\t\t\tclean_merge = -1;\n-\t\t\telse if (opt->call_depth)\n+\t\t\telse if (opt->priv->call_depth)\n \t\t\t\tremove_file_from_index(opt->repo->index, path);\n \t\t\tfree(new_path);\n \t\t} else {\n@@ -3407,7 +3417,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \tcode = unpack_trees_start(opt, merge_base, head, merge);\n \n \tif (code != 0) {\n-\t\tif (show(opt, 4) || opt->call_depth)\n+\t\tif (show(opt, 4) || opt->priv->call_depth)\n \t\t\terr(opt, _(\"merging of trees %s and %s failed\"),\n \t\t\t    oid_to_hex(&head->object.oid),\n \t\t\t    oid_to_hex(&merge->object.oid));\n@@ -3426,7 +3436,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\t * opposed to decaring a local hashmap is for convenience\n \t\t * so that we don't have to pass it to around.\n \t\t */\n-\t\thashmap_init(&opt->current_file_dir_set, path_hashmap_cmp,\n+\t\thashmap_init(&opt->priv->current_file_dir_set, path_hashmap_cmp,\n \t\t\t     NULL, 512);\n \t\tget_files_dirs(opt, head);\n \t\tget_files_dirs(opt, merge);\n@@ -3463,7 +3473,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free(&opt->current_file_dir_set, 1);\n+\t\thashmap_free(&opt->priv->current_file_dir_set, 1);\n \n \t\tif (clean < 0) {\n \t\t\tunpack_trees_finish(opt);\n@@ -3475,7 +3485,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \n \tunpack_trees_finish(opt);\n \n-\tif (opt->call_depth &&\n+\tif (opt->priv->call_depth &&\n \t    !(*result = write_in_core_index_as_tree(opt->repo)))\n \t\treturn -1;\n \n@@ -3550,7 +3560,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \n \tfor (iter = merge_bases; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n-\t\topt->call_depth++;\n+\t\topt->priv->call_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n \t\t * with conflict markers. The cleanness flag is\n@@ -3569,14 +3579,14 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\treturn -1;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n-\t\topt->call_depth--;\n+\t\topt->priv->call_depth--;\n \n \t\tif (!merged_merge_bases)\n \t\t\treturn err(opt, _(\"merge returned no commit\"));\n \t}\n \n \tdiscard_index(opt->repo->index);\n-\tif (!opt->call_depth)\n+\tif (!opt->priv->call_depth)\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = ancestor_name;\n@@ -3592,7 +3602,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\treturn clean;\n \t}\n \n-\tif (opt->call_depth) {\n+\tif (opt->priv->call_depth) {\n \t\t*result = make_virtual_commit(opt->repo, result_tree,\n \t\t\t\t\t      \"merged tree\");\n \t\tcommit_list_insert(h1, &(*result)->parents);\n@@ -3612,17 +3622,20 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n \t\treturn -1;\n \t}\n \n+\topt->priv = xcalloc(1, sizeof(*opt->priv));\n+\tstring_list_init(&opt->priv->df_conflict_file_set, 1);\n \treturn 0;\n }\n \n static void merge_finalize(struct merge_options *opt)\n {\n \tflush_output(opt);\n-\tif (!opt->call_depth && opt->buffer_output < 2)\n+\tif (!opt->priv->call_depth && opt->buffer_output < 2)\n \t\tstrbuf_release(&opt->obuf);\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opt->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0);\n+\tFREE_AND_NULL(opt->priv);\n }\n \n int merge_trees(struct merge_options *opt,\n@@ -3767,8 +3780,6 @@ void init_merge_options(struct merge_options *opt,\n \n \topt->renormalize = 0;\n \n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n-\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 933d6e7642..58a4c5238a 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -1,13 +1,15 @@\n #ifndef MERGE_RECURSIVE_H\n #define MERGE_RECURSIVE_H\n \n-#include \"string-list.h\"\n-#include \"unpack-trees.h\"\n+#include \"strbuf.h\"\n \n struct commit;\n-\n+struct commit_list;\n+struct object_id;\n struct repository;\n+struct tree;\n \n+struct merge_options_internal;\n struct merge_options {\n \tstruct repository *repo;\n \n@@ -45,13 +47,8 @@ struct merge_options {\n \tconst char *subtree_shift;\n \tunsigned renormalize : 1;\n \n-\t/* internal fields used by the implementation (do NOT set these) */\n-\tint call_depth;\n-\tint needed_rename_limit;\n-\tstruct hashmap current_file_dir_set;\n-\tstruct string_list df_conflict_file_set;\n-\tstruct unpack_trees_options unpack_opts;\n-\tstruct index_state orig_index;\n+\t/* internal fields used by the implementation */\n+\tstruct merge_options_internal *priv;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380604","messageId":"20190817184144.32179-20-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 19/24] merge-recursive: comment and reorder the merge_options fields","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:39Z","receivedAt":"2019-08-17T18:42:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The merge_options struct had lots of fields, making it a little\nimposing, but the options naturally fall into multiple different groups.\nGrouping similar options and adding a comment or two makes it easier to\nread, easier for new folks to figure out which options are related, and\nthus easier for them to find the options they need.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 16 +++++++++++-----\n merge-recursive.h | 40 ++++++++++++++++++++++++++--------------\n 2 files changed, 37 insertions(+), 19 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0f0b952c04..43dec3307e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3754,21 +3754,27 @@ void init_merge_options(struct merge_options *opt,\n {\n \tconst char *merge_verbosity;\n \tmemset(opt, 0, sizeof(struct merge_options));\n+\n \topt->repo = repo;\n+\n+\topt->detect_renames = -1;\n+\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\topt->rename_limit = -1;\n+\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->rename_limit = -1;\n+\tstrbuf_init(&opt->obuf, 0);\n+\n \topt->renormalize = 0;\n-\topt->detect_renames = -1;\n-\topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n+\n+\tstring_list_init(&opt->df_conflict_file_set, 1);\n+\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n \tif (merge_verbosity)\n \t\topt->verbosity = strtol(merge_verbosity, NULL, 10);\n \tif (opt->verbosity >= 5)\n \t\topt->buffer_output = 0;\n-\tstrbuf_init(&opt->obuf, 0);\n-\tstring_list_init(&opt->df_conflict_file_set, 1);\n }\n \n int parse_merge_opt(struct merge_options *opt, const char *s)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex f4bdfbc897..9e040608fe 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -9,36 +9,48 @@ struct commit;\n struct repository;\n \n struct merge_options {\n+\tstruct repository *repo;\n+\n+\t/* ref names used in console messages and conflict markers */\n \tconst char *ancestor;\n \tconst char *branch1;\n \tconst char *branch2;\n-\tenum {\n-\t\tMERGE_RECURSIVE_NORMAL = 0,\n-\t\tMERGE_RECURSIVE_OURS,\n-\t\tMERGE_RECURSIVE_THEIRS\n-\t} recursive_variant;\n-\tconst char *subtree_shift;\n-\tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n-\tunsigned renormalize : 1;\n-\tlong xdl_opts;\n-\tint verbosity;\n+\n+\t/* rename related options */\n+\tint detect_renames;\n \tenum {\n \t\tMERGE_DIRECTORY_RENAMES_NONE = 0,\n \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n \t} detect_directory_renames;\n-\tint detect_renames;\n \tint rename_limit;\n \tint rename_score;\n-\tint needed_rename_limit;\n \tint show_rename_progress;\n+\n+\t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n+\tlong xdl_opts;\n+\tenum {\n+\t\tMERGE_RECURSIVE_NORMAL = 0,\n+\t\tMERGE_RECURSIVE_OURS,\n+\t\tMERGE_RECURSIVE_THEIRS\n+\t} recursive_variant;\n+\n+\t/* console output related options */\n+\tint verbosity;\n+\tunsigned buffer_output; /* 1: output at end, 2: keep buffered */\n+\tstruct strbuf obuf;     /* output buffer */\n+\n+\t/* miscellaneous control options */\n+\tconst char *subtree_shift;\n+\tunsigned renormalize : 1;\n+\n+\t/* internal fields used by the implementation (do NOT set these) */\n \tint call_depth;\n-\tstruct strbuf obuf;\n+\tint needed_rename_limit;\n \tstruct hashmap current_file_dir_set;\n \tstruct string_list df_conflict_file_set;\n \tstruct unpack_trees_options unpack_opts;\n \tstruct index_state orig_index;\n-\tstruct repository *repo;\n };\n \n void init_merge_options(struct merge_options *opt, struct repository *repo);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380607","messageId":"20190817184144.32179-19-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 18/24] merge-recursive: consolidate unnecessary fields in merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:38Z","receivedAt":"2019-08-17T18:42:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We provided users with the ability to state whether they wanted rename\ndetection, and to put a limit on how much CPU would be spent.  Both of\nthese fields had multiple configuration parameters for setting them,\nwith one being a fallback and the other being an override.  However,\ninstead of implementing the logic for how to combine the multiple\nsource locations into the appropriate setting at config loading time,\nwe loaded and tracked both values and then made the code combine them\nevery time it wanted to check the overall value.  This had a few\nminor drawbacks:\n  * it seems more complicated than necessary\n  * it runs the risk of people using the independent settings in the\n    future and breaking the intent of how the options are used\n    together\n  * it makes merge_options more complicated than necessary for other\n    potential users of the API\n\nFix these problems by moving the logic for combining the pairs of\noptions into a single value; make it apply at time-of-config-loading\ninstead of each-time-of-use.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 27 +++++++++++----------------\n merge-recursive.h |  6 ++----\n 2 files changed, 13 insertions(+), 20 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 9807b24c65..0f0b952c04 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -385,8 +385,7 @@ static int add_cacheinfo(struct merge_options *opt,\n \n static inline int merge_detect_rename(struct merge_options *opt)\n {\n-\treturn opt->merge_detect_rename >= 0 ? opt->merge_detect_rename :\n-\t\topt->diff_detect_rename >= 0 ? opt->diff_detect_rename : 1;\n+\treturn (opt->detect_renames >= 0) ? opt->detect_renames : 1;\n }\n \n static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)\n@@ -1883,9 +1882,7 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,\n \t */\n \tif (opts.detect_rename > DIFF_DETECT_RENAME)\n \t\topts.detect_rename = DIFF_DETECT_RENAME;\n-\topts.rename_limit = opt->merge_rename_limit >= 0 ? opt->merge_rename_limit :\n-\t\t\t    opt->diff_rename_limit >= 0 ? opt->diff_rename_limit :\n-\t\t\t    1000;\n+\topts.rename_limit = (opt->rename_limit >= 0) ? opt->rename_limit : 1000;\n \topts.rename_score = opt->rename_score;\n \topts.show_rename_progress = opt->show_rename_progress;\n \topts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -3727,14 +3724,14 @@ static void merge_recursive_config(struct merge_options *opt)\n {\n \tchar *value = NULL;\n \tgit_config_get_int(\"merge.verbosity\", &opt->verbosity);\n-\tgit_config_get_int(\"diff.renamelimit\", &opt->diff_rename_limit);\n-\tgit_config_get_int(\"merge.renamelimit\", &opt->merge_rename_limit);\n+\tgit_config_get_int(\"diff.renamelimit\", &opt->rename_limit);\n+\tgit_config_get_int(\"merge.renamelimit\", &opt->rename_limit);\n \tif (!git_config_get_string(\"diff.renames\", &value)) {\n-\t\topt->diff_detect_rename = git_config_rename(\"diff.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"diff.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.renames\", &value)) {\n-\t\topt->merge_detect_rename = git_config_rename(\"merge.renames\", value);\n+\t\topt->detect_renames = git_config_rename(\"merge.renames\", value);\n \t\tfree(value);\n \t}\n \tif (!git_config_get_string(\"merge.directoryrenames\", &value)) {\n@@ -3760,11 +3757,9 @@ void init_merge_options(struct merge_options *opt,\n \topt->repo = repo;\n \topt->verbosity = 2;\n \topt->buffer_output = 1;\n-\topt->diff_rename_limit = -1;\n-\topt->merge_rename_limit = -1;\n+\topt->rename_limit = -1;\n \topt->renormalize = 0;\n-\topt->diff_detect_rename = -1;\n-\topt->merge_detect_rename = -1;\n+\topt->detect_renames = -1;\n \topt->detect_directory_renames = MERGE_DIRECTORY_RENAMES_CONFLICT;\n \tmerge_recursive_config(opt);\n \tmerge_verbosity = getenv(\"GIT_MERGE_VERBOSITY\");\n@@ -3816,16 +3811,16 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \telse if (!strcmp(s, \"no-renormalize\"))\n \t\topt->renormalize = 0;\n \telse if (!strcmp(s, \"no-renames\"))\n-\t\topt->merge_detect_rename = 0;\n+\t\topt->detect_renames = 0;\n \telse if (!strcmp(s, \"find-renames\")) {\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t\topt->rename_score = 0;\n \t}\n \telse if (skip_prefix(s, \"find-renames=\", &arg) ||\n \t\t skip_prefix(s, \"rename-threshold=\", &arg)) {\n \t\tif ((opt->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)\n \t\t\treturn -1;\n-\t\topt->merge_detect_rename = 1;\n+\t\topt->detect_renames = 1;\n \t}\n \t/*\n \t * Please update $__git_merge_strategy_options in\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 0fdae904dd..f4bdfbc897 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -27,10 +27,8 @@ struct merge_options {\n \t\tMERGE_DIRECTORY_RENAMES_CONFLICT = 1,\n \t\tMERGE_DIRECTORY_RENAMES_TRUE = 2\n \t} detect_directory_renames;\n-\tint diff_detect_rename;\n-\tint merge_detect_rename;\n-\tint diff_rename_limit;\n-\tint merge_rename_limit;\n+\tint detect_renames;\n+\tint rename_limit;\n \tint rename_score;\n \tint needed_rename_limit;\n \tint show_rename_progress;\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380608","messageId":"20190817184144.32179-23-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 22/24] merge-recursive: rename MERGE_RECURSIVE_* to MERGE_VARIANT_*","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:42Z","receivedAt":"2019-08-17T18:42:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"I want to implement the same outward facing API as found within\nmerge-recursive.h in a different merge strategy.  However, that makes\nnames like MERGE_RECURSIVE_{NORMAL,OURS,THEIRS} look a little funny;\nrename to MERGE_VARIANT_{NORMAL,OURS,THEIRS}.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 14 +++++++-------\n merge-recursive.h |  6 +++---\n 2 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c92993e8f0..fa3f8eb0b2 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1045,10 +1045,10 @@ static int merge_3way(struct merge_options *opt,\n \t\tll_opts.variant = 0;\n \t} else {\n \t\tswitch (opt->recursive_variant) {\n-\t\tcase MERGE_RECURSIVE_OURS:\n+\t\tcase MERGE_VARIANT_OURS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_OURS;\n \t\t\tbreak;\n-\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\tll_opts.variant = XDL_MERGE_FAVOR_THEIRS;\n \t\t\tbreak;\n \t\tdefault:\n@@ -1355,15 +1355,15 @@ static int merge_mode_and_contents(struct merge_options *opt,\n \t\t\t\t\t\t\t&b->oid);\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\tswitch (opt->recursive_variant) {\n-\t\t\tcase MERGE_RECURSIVE_NORMAL:\n+\t\t\tcase MERGE_VARIANT_NORMAL:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tif (!oid_eq(&a->oid, &b->oid))\n \t\t\t\t\tresult->clean = 0;\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_OURS:\n+\t\t\tcase MERGE_VARIANT_OURS:\n \t\t\t\toidcpy(&result->blob.oid, &a->oid);\n \t\t\t\tbreak;\n-\t\t\tcase MERGE_RECURSIVE_THEIRS:\n+\t\t\tcase MERGE_VARIANT_THEIRS:\n \t\t\t\toidcpy(&result->blob.oid, &b->oid);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -3795,9 +3795,9 @@ int parse_merge_opt(struct merge_options *opt, const char *s)\n \tif (!s || !*s)\n \t\treturn -1;\n \tif (!strcmp(s, \"ours\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_OURS;\n+\t\topt->recursive_variant = MERGE_VARIANT_OURS;\n \telse if (!strcmp(s, \"theirs\"))\n-\t\topt->recursive_variant = MERGE_RECURSIVE_THEIRS;\n+\t\topt->recursive_variant = MERGE_VARIANT_THEIRS;\n \telse if (!strcmp(s, \"subtree\"))\n \t\topt->subtree_shift = \"\";\n \telse if (skip_prefix(s, \"subtree=\", &arg))\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 58a4c5238a..978847e672 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -32,9 +32,9 @@ struct merge_options {\n \t/* xdiff-related options (patience, ignore whitespace, ours/theirs) */\n \tlong xdl_opts;\n \tenum {\n-\t\tMERGE_RECURSIVE_NORMAL = 0,\n-\t\tMERGE_RECURSIVE_OURS,\n-\t\tMERGE_RECURSIVE_THEIRS\n+\t\tMERGE_VARIANT_NORMAL = 0,\n+\t\tMERGE_VARIANT_OURS,\n+\t\tMERGE_VARIANT_THEIRS\n \t} recursive_variant;\n \n \t/* console output related options */\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380609","messageId":"20190817184144.32179-25-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 24/24] merge-recursive: alphabetize include list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:44Z","receivedAt":"2019-08-17T18:42:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Other than cache.h which needs to appear first, and merge-recursive.h\nwhich I want to be second so that we are more likely to notice if\nmerge-recursive.h has any missing includes, the rest of the list is\nlong and easier to look through if it's alphabetical.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 33 +++++++++++++++++----------------\n 1 file changed, 17 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0231d7b049..b058741f00 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -4,30 +4,31 @@\n  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006\n  */\n #include \"cache.h\"\n-#include \"config.h\"\n+#include \"merge-recursive.h\"\n+\n #include \"advice.h\"\n-#include \"lockfile.h\"\n-#include \"cache-tree.h\"\n-#include \"object-store.h\"\n-#include \"repository.h\"\n-#include \"commit.h\"\n+#include \"alloc.h\"\n+#include \"attr.h\"\n #include \"blob.h\"\n #include \"builtin.h\"\n-#include \"tree-walk.h\"\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"commit-reach.h\"\n+#include \"config.h\"\n #include \"diff.h\"\n #include \"diffcore.h\"\n+#include \"dir.h\"\n+#include \"ll-merge.h\"\n+#include \"lockfile.h\"\n+#include \"object-store.h\"\n+#include \"repository.h\"\n+#include \"revision.h\"\n+#include \"string-list.h\"\n+#include \"submodule.h\"\n #include \"tag.h\"\n-#include \"alloc.h\"\n+#include \"tree-walk.h\"\n #include \"unpack-trees.h\"\n-#include \"string-list.h\"\n #include \"xdiff-interface.h\"\n-#include \"ll-merge.h\"\n-#include \"attr.h\"\n-#include \"merge-recursive.h\"\n-#include \"dir.h\"\n-#include \"submodule.h\"\n-#include \"revision.h\"\n-#include \"commit-reach.h\"\n \n struct merge_options_internal {\n \tint call_depth;\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380610","messageId":"20190817184144.32179-24-newren@gmail.com","threadId":"51526","inReplyTo":"20190817184144.32179-1-newren@gmail.com","subject":"[PATCH v4 23/24] merge-recursive: add sanity checks for relevant merge_options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-08-17T18:41:43Z","receivedAt":"2019-08-17T18:42:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are lots of options that callers can set, yet most have a limited\nrange of valid values, some options are meant for output (e.g.\nopt->obuf, which is expected to start empty), and callers are expected\nto not set opt->priv.  Add several sanity checks to ensure callers\nprovide sane values.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 24 ++++++++++++++++++++++++\n 1 file changed, 24 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fa3f8eb0b2..0231d7b049 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3615,6 +3615,30 @@ static int merge_start(struct merge_options *opt, struct tree *head)\n {\n \tstruct strbuf sb = STRBUF_INIT;\n \n+\t/* Sanity checks on opt */\n+\tassert(opt->repo);\n+\n+\tassert(opt->branch1 && opt->branch2);\n+\n+\tassert(opt->detect_renames >= -1 &&\n+\t       opt->detect_renames <= DIFF_DETECT_COPY);\n+\tassert(opt->detect_directory_renames >= MERGE_DIRECTORY_RENAMES_NONE &&\n+\t       opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE);\n+\tassert(opt->rename_limit >= -1);\n+\tassert(opt->rename_score >= 0 && opt->rename_score <= MAX_SCORE);\n+\tassert(opt->show_rename_progress >= 0 && opt->show_rename_progress <= 1);\n+\n+\tassert(opt->xdl_opts >= 0);\n+\tassert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&\n+\t       opt->recursive_variant <= MERGE_VARIANT_THEIRS);\n+\n+\tassert(opt->verbosity >= 0 && opt->verbosity <= 5);\n+\tassert(opt->buffer_output <= 2);\n+\tassert(opt->obuf.len == 0);\n+\n+\tassert(opt->priv == NULL);\n+\n+\t/* Sanity check on repo state; index must match head */\n \tif (repo_index_has_changes(opt->repo, head, &sb)) {\n \t\terr(opt, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n \t\t    sb.buf);\n-- \n2.23.0.rc2.28.g5f89f15d7b.dirty\n\n"},{"id":"380716","messageId":"xmqqy2zpxefn.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20190817184144.32179-22-newren@gmail.com","subject":"Re: [PATCH v4 21/24] merge-recursive: split internal fields into a separate struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-19T17:17:32Z","receivedAt":"2019-08-19T17:17:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> merge_options has several internal fields that should not be set or read\n> by external callers.  This just complicates the API.  Move them into an\n> opaque merge_options_internal struct that is defined only in\n> merge-recursive.c and keep these out of merge-recursive.h.\n\nMakes quite a lot of sense.  Thanks.\n"},{"id":"381696","messageId":"nycvar.QRO.7.76.6.1909030043460.46@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"20190817184144.32179-9-newren@gmail.com","subject":"Re: [PATCH v4 08/24] Ensure index matches head before invoking merge machinery, round N","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-09-02T23:01:15Z","receivedAt":"2019-09-02T23:01:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 17 Aug 2019, Elijah Newren wrote:\n\n>   * t3030-merge-recursive.h: this test has always been broken in that it\n>     didn't make sure to make index match head before running.  But, it\n>     didn't care about the index or even the merge result, just the\n>     verbose output while running.  While commit eddd1a411d93\n>     (\"merge-recursive: enforce rule that index matches head before\n>     merging\", 2018-06-30) should have uncovered this broken test, it\n>     used a test_must_fail wrapper around the merge-recursive call\n>     because it was known that the merge resulted in a rename/rename\n>     conflict.  Thus, that fix only made this test fail for a different\n>     reason, and since the index == head check didn't happen until after\n>     coming all the way back out of the recursion, the testcase had\n>     enough information to pass the one check that it did perform.\n\nI fear that this test is still broken, it is a Schrödinger bug. Where\n`qsort()` is the cat, and the property \"is it stable?\" instead of death.\n\nIn particular, at some stage in the recursive merge, a diff is generated\nwith rename detection for the target file `a` that contains two lines `hello`\nbut has two equally valid source files: `e` and `a~Temporary merge\nbranch 2_0`, both containing just the line `hello`. And since their file\ncontents are identical, the solution to the problem \"which is the\ncorrect source file?\" is ambiguous.\n\nIf the `qsort()` in use is stable, the file `e` comes first, and wins.\nIf the `qsort()` in use is not stable, all bets are off, and the file\n`a~Temporary merge branch 2_0` might be sorted first (which is the case,\nfor example, when using the `qsort()` implementation of MS Visual C's\nruntime).\n\nNow, the _real_ problem is that t3030.35 expects the recursive merge to\nfail, which it does when `qsort()` is stable. However, when the order of\n`e` and `a~Temporary merge branch 2_0` is reversed, then that particular\nmerge does _not_ result in a `rename/rename` conflict. And the exit code\nof the recursive merge is 0 for some reason!\n\nI don't quite understand why: clearly, there are conflicts (otherwise we\nwould not have that funny suffix `~Temporary merge branch 2_0`.\n\nThe real problem, though, is that even if it would fail, the outcome of\nthat recursive merge is ambiguous, and that test case should not try to\nverify the precise order of the generated intermediate trees.\n\nCiao,\nDscho\n"},{"id":"381722","messageId":"nycvar.QRO.7.76.6.1909031532160.46@tvgsbejvaqbjf.bet","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1909030043460.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 08/24] Ensure index matches head before invoking merge machinery, round N","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-09-03T13:34:39Z","receivedAt":"2019-09-03T13:34:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Tue, 3 Sep 2019, Johannes Schindelin wrote:\n\n> On Sat, 17 Aug 2019, Elijah Newren wrote:\n>\n> >   * t3030-merge-recursive.h: this test has always been broken in that it\n> >     didn't make sure to make index match head before running.  But, it\n> >     didn't care about the index or even the merge result, just the\n> >     verbose output while running.  While commit eddd1a411d93\n> >     (\"merge-recursive: enforce rule that index matches head before\n> >     merging\", 2018-06-30) should have uncovered this broken test, it\n> >     used a test_must_fail wrapper around the merge-recursive call\n> >     because it was known that the merge resulted in a rename/rename\n> >     conflict.  Thus, that fix only made this test fail for a different\n> >     reason, and since the index == head check didn't happen until after\n> >     coming all the way back out of the recursion, the testcase had\n> >     enough information to pass the one check that it did perform.\n>\n> I fear that this test is still broken, it is a Schrödinger bug. Where\n> `qsort()` is the cat, and the property \"is it stable?\" instead of death.\n>\n> In particular, at some stage in the recursive merge, a diff is generated\n> with rename detection for the target file `a` that contains two lines `hello`\n> but has two equally valid source files: `e` and `a~Temporary merge\n> branch 2_0`, both containing just the line `hello`. And since their file\n> contents are identical, the solution to the problem \"which is the\n> correct source file?\" is ambiguous.\n>\n> If the `qsort()` in use is stable, the file `e` comes first, and wins.\n> If the `qsort()` in use is not stable, all bets are off, and the file\n> `a~Temporary merge branch 2_0` might be sorted first (which is the case,\n> for example, when using the `qsort()` implementation of MS Visual C's\n> runtime).\n>\n> Now, the _real_ problem is that t3030.35 expects the recursive merge to\n> fail, which it does when `qsort()` is stable. However, when the order of\n> `e` and `a~Temporary merge branch 2_0` is reversed, then that particular\n> merge does _not_ result in a `rename/rename` conflict. And the exit code\n> of the recursive merge is 0 for some reason!\n>\n> I don't quite understand why: clearly, there are conflicts (otherwise we\n> would not have that funny suffix `~Temporary merge branch 2_0`.\n>\n> The real problem, though, is that even if it would fail, the outcome of\n> that recursive merge is ambiguous, and that test case should not try to\n> verify the precise order of the generated intermediate trees.\n\nIt might not be obvious from my mail, but it took me about 7 hours to\nfigure all of this out, hence I was a bit grumpy when I wrote that. My\napologies.\n\nAfter having slept (and written a long review about the\n`--update-branches` patch), it occurred to me that we might be better\noff enforcing the use of `git_qsort()` in `diffcore-rename.c`, so that\nwe can at least guarantee stable rename detection in Git (which would\nincidentally fix the test suite for the MSVC build that I maintain in\nGit for Windows).\n\nWhat do you think?\n\nCiao,\nDscho\n"},{"id":"381747","messageId":"CABPp-BF4yjZrD7-bXLa9tHgEws2zvN2zJxqnMqgtY6zm2Lvk3A@mail.gmail.com","threadId":"51526","inReplyTo":"nycvar.QRO.7.76.6.1909031532160.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v4 08/24] Ensure index matches head before invoking merge machinery, round N","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-09-03T18:17:04Z","receivedAt":"2019-09-03T18:17:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Tue, Sep 3, 2019 at 6:34 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Tue, 3 Sep 2019, Johannes Schindelin wrote:\n>\n> > On Sat, 17 Aug 2019, Elijah Newren wrote:\n> >\n> > >   * t3030-merge-recursive.h: this test has always been broken in that it\n> > >     didn't make sure to make index match head before running.  But, it\n> > >     didn't care about the index or even the merge result, just the\n> > >     verbose output while running.  While commit eddd1a411d93\n> > >     (\"merge-recursive: enforce rule that index matches head before\n> > >     merging\", 2018-06-30) should have uncovered this broken test, it\n> > >     used a test_must_fail wrapper around the merge-recursive call\n> > >     because it was known that the merge resulted in a rename/rename\n> > >     conflict.  Thus, that fix only made this test fail for a different\n> > >     reason, and since the index == head check didn't happen until after\n> > >     coming all the way back out of the recursion, the testcase had\n> > >     enough information to pass the one check that it did perform.\n> >\n> > I fear that this test is still broken, it is a Schrödinger bug. Where\n> > `qsort()` is the cat, and the property \"is it stable?\" instead of death.\n> >\n> > In particular, at some stage in the recursive merge, a diff is generated\n> > with rename detection for the target file `a` that contains two lines `hello`\n> > but has two equally valid source files: `e` and `a~Temporary merge\n> > branch 2_0`, both containing just the line `hello`. And since their file\n> > contents are identical, the solution to the problem \"which is the\n> > correct source file?\" is ambiguous.\n> >\n> > If the `qsort()` in use is stable, the file `e` comes first, and wins.\n> > If the `qsort()` in use is not stable, all bets are off, and the file\n> > `a~Temporary merge branch 2_0` might be sorted first (which is the case,\n> > for example, when using the `qsort()` implementation of MS Visual C's\n> > runtime).\n> >\n> > Now, the _real_ problem is that t3030.35 expects the recursive merge to\n> > fail, which it does when `qsort()` is stable. However, when the order of\n> > `e` and `a~Temporary merge branch 2_0` is reversed, then that particular\n> > merge does _not_ result in a `rename/rename` conflict. And the exit code\n> > of the recursive merge is 0 for some reason!\n> >\n> > I don't quite understand why: clearly, there are conflicts (otherwise we\n> > would not have that funny suffix `~Temporary merge branch 2_0`.\n\nSo, there are conflicts in the inner merge, but depending on the\ntie-breaker for rename handling when two equal matches exist (the\ntie-breaker being the order of the filenames after qsort()), there may\nor may not be conflicts in the outer merge.  Ouch.\n\nI suspect such cases are pretty rare in \"real world repositories\"\nbecause (1) exactly equal filename similarities are rare, (2) \"slowly\nchanging trees of content\" implies that most files will only be\nmodified on (at most) one side of history, (3) when files are changed\non both sides of history odds of conflicting changes rapidly go up\nmaking conflicts likely.  You essentially have to thread a needle to\nhave the end result ambiguously conflict like this.\n\n> > The real problem, though, is that even if it would fail, the outcome of\n> > that recursive merge is ambiguous, and that test case should not try to\n> > verify the precise order of the generated intermediate trees.\n\nYes, it is ambiguous -- and the problem is a little deeper too. It's\nnot just \"does-this-merge-conflict?\" depending upon the qsort order,\nit is also about whether file contents after the merge depend upon the\nqsort order.  Whenever there are two filenames that are equally\nsimilar to a rename source, picking one of the two equally similar\nfilenames for rename pairing means we are basically choosing at random\nwhere to merge the changes from the other side of history to.\n\nUnfortunately, changing this might be difficult to enforce with the\ncurrent merge-recursive structure.  For example, what if there are two\nequally similar filenames for us to choose from, but the other side of\nhistory didn't modify the rename source file at all?  (e.g. on one\nside of history, user leaves A alone, on other side of history, A is\ncopied to B and C and then A is deleted.  B and C are identical.)  In\nsuch cases, the choice of which of B and C we pair A up with happens\nto be irrelevant because we'll get the same result either way and\nthere should be no merge conflict.  But if we error out early or throw\nwarnings and conflict notices because the intermediate internal choice\nwas ambiguous, then we've created useless conflicts for the user.  I'm\nworried we have more cases of this kind of thing happening than we do\nwith ambiguous pairings that change the end result.\n\nI think I might be able to do something here with my alternative merge\nstrategy, but I haven't gotten back to that for quite a while, so...\n\n> It might not be obvious from my mail, but it took me about 7 hours to\n> figure all of this out, hence I was a bit grumpy when I wrote that. My\n> apologies.\n>\n> After having slept (and written a long review about the\n> `--update-branches` patch), it occurred to me that we might be better\n> off enforcing the use of `git_qsort()` in `diffcore-rename.c`, so that\n> we can at least guarantee stable rename detection in Git (which would\n> incidentally fix the test suite for the MSVC build that I maintain in\n> Git for Windows).\n>\n> What do you think?\n\nOoh, absolutely, we should do that in the short term.  Not just\nbecause it fixes the testsuite, but because it increases the\nlikelihood that folks can reproduce each others' merge problems.  I\nwant to be able to help users on Windows who report problems and\nprovide testcases, and making this fix reduces one hurdle toward doing\nso.\n"},{"id":"383176","messageId":"20190930211437.GA28110@sigill.intra.peff.net","threadId":"51526","inReplyTo":"20190817184144.32179-5-newren@gmail.com","subject":"Re: [PATCH v4 04/24] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-09-30T21:14:37Z","receivedAt":"2019-09-30T21:14:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Aug 17, 2019 at 11:41:24AM -0700, Elijah Newren wrote:\n\n> In commit 7ca56aa07619 (\"merge-recursive: add a label for ancestor\",\n> 2010-03-20), a label was added for the '||||||' line to make it have\n> the more informative heading '|||||| merged common ancestors', with\n> the statement:\n> \n>     It would be nicer to use a more informative label.  Perhaps someone\n>     will provide one some day.\n> \n> This chosen label was perfectly reasonable when recursiveness kicks in,\n> i.e. when there are multiple merge bases.  (I can't think of a better\n> label in such cases.)  But it is actually somewhat misleading when there\n> is a unique merge base or no merge base.  Change this based on the\n> number of merge bases:\n>     >=2: \"merged common ancestors\"\n>     1:   <abbreviated commit hash>\n>     0:   \"<empty tree>\"\n\nI got a funny result from this today while rebasing some patches in\ngit.git, where the base is reported as \"00000000\". I didn't make a\nminimal case, but you can easily reproduce it with:\n\n  cd /your/git/clone\n  git fetch https://github.com/peff/git odd-diff3-base\n  git checkout -b odd-diff3-base FETCH_HEAD\n  git -c merge.conflictstyle=diff3 rebase --onto origin/master HEAD~2\n\nMaybe this has to do with \"git apply --build-fake-ancestor\" being used\nunder the hood?\n\n-Peff\n"},{"id":"383178","messageId":"20190930211909.GA29232@sigill.intra.peff.net","threadId":"51526","inReplyTo":"20190930211437.GA28110@sigill.intra.peff.net","subject":"Re: [PATCH v4 04/24] merge-recursive: provide a better label for diff3 common ancestor","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-09-30T21:19:09Z","receivedAt":"2019-09-30T21:19:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Sep 30, 2019 at 05:14:37PM -0400, Jeff King wrote:\n\n> > This chosen label was perfectly reasonable when recursiveness kicks in,\n> > i.e. when there are multiple merge bases.  (I can't think of a better\n> > label in such cases.)  But it is actually somewhat misleading when there\n> > is a unique merge base or no merge base.  Change this based on the\n> > number of merge bases:\n> >     >=2: \"merged common ancestors\"\n> >     1:   <abbreviated commit hash>\n> >     0:   \"<empty tree>\"\n> \n> I got a funny result from this today while rebasing some patches in\n> git.git, where the base is reported as \"00000000\". I didn't make a\n> minimal case, but you can easily reproduce it with:\n> \n>   cd /your/git/clone\n>   git fetch https://github.com/peff/git odd-diff3-base\n>   git checkout -b odd-diff3-base FETCH_HEAD\n>   git -c merge.conflictstyle=diff3 rebase --onto origin/master HEAD~2\n> \n> Maybe this has to do with \"git apply --build-fake-ancestor\" being used\n> under the hood?\n\nOh, indeed, this seems to be the case for all rebases. Doing:\n\n  git init repo && cd repo\n  \n  echo base >file && git add file && git commit -m base\n  echo master >file && git commit -am master\n  git checkout -b side HEAD^\n  echo side >file && git commit -am side\n  git config merge.conflictstyle diff3\n  \n  git rebase master\n\nyields:\n\n  <<<<<<< HEAD\n  master\n  ||||||| 0000000\n  base\n  =======\n  side\n  >>>>>>> side\n\n-Peff\n"},{"id":"383185","messageId":"20190930225455.6208-1-newren@gmail.com","threadId":"51526","inReplyTo":"20190930211437.GA28110@sigill.intra.peff.net","subject":"[PATCH] merge-recursive: fix the diff3 common ancestor label for virtual commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-09-30T22:54:55Z","receivedAt":"2019-09-30T22:55:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 743474cbfa8b (\"merge-recursive: provide a better label for\ndiff3 common ancestor\", 2019-08-17), the label for the common ancestor\nwas changed from always being\n\n         \"merged common ancestors\"\n\nto instead be based on the number of merge bases:\n\n    >=2: \"merged common ancestors\"\n      1: <abbreviated commit hash>\n      0: \"<empty tree>\"\n\nUnfortunately, this did not take into account that when we have a single\nmerge base, that merge base could be fake or constructed.  In such\ncases, this resulted in a label of \"00000000\".  Of course, the previous\nlabel of \"merged common ancestors\" was also misleading for these cases.\nSince we have an API that is explicitly about creating fake commits in\nmerge_recursive_generic(), we should provide a better label when using\nthat API.  So, when merge_recursive_generic() is called, set the label\nto:\n\n         \"constructed merge base\"\n\nNote that users of merge_recursive_generic include the builtin commands\ngit-am (in combination with git apply --build-fake-ancestor),\ngit-merge-recursive, and git-stash.\n\nReported-by: Jeff King <peff@peff.net>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nApplies to the top of en/merge-recursive-cleanup, which is in next.\n\n merge-recursive.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b058741f00..2b7722e350 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3550,6 +3550,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n \t\t\t\t\t\t\t \"ancestor\");\n \t\tancestor_name = \"empty tree\";\n+\t} else if (opt->ancestor) {\n+\t\tancestor_name = opt->ancestor;\n \t} else if (merge_bases) {\n \t\tancestor_name = \"merged common ancestors\";\n \t} else {\n@@ -3689,7 +3691,8 @@ int merge_recursive(struct merge_options *opt,\n {\n \tint clean;\n \n-\tassert(opt->ancestor == NULL);\n+\tassert(opt->ancestor == NULL ||\n+\t       !strcmp(opt->ancestor, \"constructed merge base\"));\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n@@ -3741,6 +3744,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n+\t\topt->ancestor = \"constructed merge base\";\n \t}\n \n \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\n-- \n2.23.0.25.ge3b5015985\n\n"},{"id":"383192","messageId":"CABPp-BGreoki4M8waiiSsF2NVf+2B_NFs3qFzsg2WQ6zQpNe7g@mail.gmail.com","threadId":"51526","inReplyTo":"20190930225455.6208-1-newren@gmail.com","subject":"Re: [PATCH] merge-recursive: fix the diff3 common ancestor label for virtual commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-10-01T06:56:43Z","receivedAt":"2019-10-01T06:58:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Sep 30, 2019 at 3:55 PM Elijah Newren <newren@gmail.com> wrote:\n>\n> In commit 743474cbfa8b (\"merge-recursive: provide a better label for\n> diff3 common ancestor\", 2019-08-17), the label for the common ancestor\n> was changed from always being\n>\n>          \"merged common ancestors\"\n>\n> to instead be based on the number of merge bases:\n>\n>     >=2: \"merged common ancestors\"\n>       1: <abbreviated commit hash>\n>       0: \"<empty tree>\"\n>\n> Unfortunately, this did not take into account that when we have a single\n> merge base, that merge base could be fake or constructed.  In such\n> cases, this resulted in a label of \"00000000\".  Of course, the previous\n> label of \"merged common ancestors\" was also misleading for these cases.\n> Since we have an API that is explicitly about creating fake commits in\n> merge_recursive_generic(), we should provide a better label when using\n> that API.  So, when merge_recursive_generic() is called, set the label\n> to:\n>\n>          \"constructed merge base\"\n>\n> Note that users of merge_recursive_generic include the builtin commands\n> git-am (in combination with git apply --build-fake-ancestor),\n> git-merge-recursive, and git-stash.\n>\n> Reported-by: Jeff King <peff@peff.net>\n...\n> @@ -3741,6 +3744,7 @@ int merge_recursive_generic(struct merge_options *opt,\n>                                            oid_to_hex(merge_bases[i]));\n>                         commit_list_insert(base, &ca);\n>                 }\n> +               opt->ancestor = \"constructed merge base\";\n\nThis should have a 'if (num_merge_bases == 1)' check before it; I'll\nbe sending a v2 shortly and update the commit message slightly.\n"},{"id":"383193","messageId":"20191001065849.29491-1-newren@gmail.com","threadId":"51526","inReplyTo":"20190930225455.6208-1-newren@gmail.com","subject":"[PATCH v2] merge-recursive: fix the diff3 common ancestor label for virtual commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-10-01T06:58:49Z","receivedAt":"2019-10-01T06:58:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 743474cbfa8b (\"merge-recursive: provide a better label for\ndiff3 common ancestor\", 2019-08-17), the label for the common ancestor\nwas changed from always being\n\n         \"merged common ancestors\"\n\nto instead be based on the number of merge bases:\n\n    >=2: \"merged common ancestors\"\n      1: <abbreviated commit hash>\n      0: \"<empty tree>\"\n\nUnfortunately, this did not take into account that when we have a single\nmerge base, that merge base could be fake or constructed.  In such\ncases, this resulted in a label of \"00000000\".  Of course, the previous\nlabel of \"merged common ancestors\" was also misleading for this case.\nSince we have an API that is explicitly about creating fake merge base\ncommits in merge_recursive_generic(), we should provide a better label\nwhen using that API with one merge base.  So, when\nmerge_recursive_generic() is called with one merge base, set the label\nto:\n\n         \"constructed merge base\"\n\nNote that callers of merge_recursive_generic() include the builtin\ncommands git-am (in combination with git apply --build-fake-ancestor),\ngit-merge-recursive, and git-stash.\n\nReported-by: Jeff King <peff@peff.net>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nApplies to the top of en/merge-recursive-cleanup, which is in next.\n\nChanges since v1:\n  - We only had a problem if the number of fake merge bases was exactly\n    one; update the patch to check for that and update the commit message\n    accordingly.\n\nRange-diff:\n1:  e3b5015985 ! 1:  3f4444bfd7 merge-recursive: fix the diff3 common ancestor label for virtual commits\n    @@ Commit message\n         Unfortunately, this did not take into account that when we have a single\n         merge base, that merge base could be fake or constructed.  In such\n         cases, this resulted in a label of \"00000000\".  Of course, the previous\n    -    label of \"merged common ancestors\" was also misleading for these cases.\n    -    Since we have an API that is explicitly about creating fake commits in\n    -    merge_recursive_generic(), we should provide a better label when using\n    -    that API.  So, when merge_recursive_generic() is called, set the label\n    +    label of \"merged common ancestors\" was also misleading for this case.\n    +    Since we have an API that is explicitly about creating fake merge base\n    +    commits in merge_recursive_generic(), we should provide a better label\n    +    when using that API with one merge base.  So, when\n    +    merge_recursive_generic() is called with one merge base, set the label\n         to:\n     \n                  \"constructed merge base\"\n     \n    -    Note that users of merge_recursive_generic include the builtin commands\n    -    git-am (in combination with git apply --build-fake-ancestor),\n    +    Note that callers of merge_recursive_generic() include the builtin\n    +    commands git-am (in combination with git apply --build-fake-ancestor),\n         git-merge-recursive, and git-stash.\n     \n         Reported-by: Jeff King <peff@peff.net>\n    @@ merge-recursive.c: int merge_recursive_generic(struct merge_options *opt,\n      \t\t\t\t\t   oid_to_hex(merge_bases[i]));\n      \t\t\tcommit_list_insert(base, &ca);\n      \t\t}\n    -+\t\topt->ancestor = \"constructed merge base\";\n    ++\t\tif (num_merge_bases == 1)\n    ++\t\t\topt->ancestor = \"constructed merge base\";\n      \t}\n      \n      \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\n\n merge-recursive.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b058741f00..e12d91f48a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3550,6 +3550,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n \t\t\t\t\t\t\t \"ancestor\");\n \t\tancestor_name = \"empty tree\";\n+\t} else if (opt->ancestor) {\n+\t\tancestor_name = opt->ancestor;\n \t} else if (merge_bases) {\n \t\tancestor_name = \"merged common ancestors\";\n \t} else {\n@@ -3689,7 +3691,8 @@ int merge_recursive(struct merge_options *opt,\n {\n \tint clean;\n \n-\tassert(opt->ancestor == NULL);\n+\tassert(opt->ancestor == NULL ||\n+\t       !strcmp(opt->ancestor, \"constructed merge base\"));\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n@@ -3741,6 +3744,8 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n+\t\tif (num_merge_bases == 1)\n+\t\t\topt->ancestor = \"constructed merge base\";\n \t}\n \n \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\n-- \n2.23.0.25.ge3b5015985.dirty\n\n"},{"id":"383210","messageId":"20191001144940.GH10875@sigill.intra.peff.net","threadId":"51526","inReplyTo":"20191001065849.29491-1-newren@gmail.com","subject":"Re: [PATCH v2] merge-recursive: fix the diff3 common ancestor label for virtual commits","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2019-10-01T14:49:41Z","receivedAt":"2019-10-01T14:49:43Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Sep 30, 2019 at 11:58:49PM -0700, Elijah Newren wrote:\n\n> In commit 743474cbfa8b (\"merge-recursive: provide a better label for\n> diff3 common ancestor\", 2019-08-17), the label for the common ancestor\n> was changed from always being\n> \n>          \"merged common ancestors\"\n> \n> to instead be based on the number of merge bases:\n> \n>     >=2: \"merged common ancestors\"\n>       1: <abbreviated commit hash>\n>       0: \"<empty tree>\"\n> \n> Unfortunately, this did not take into account that when we have a single\n> merge base, that merge base could be fake or constructed.  In such\n> cases, this resulted in a label of \"00000000\".  Of course, the previous\n> label of \"merged common ancestors\" was also misleading for this case.\n\nYeah, I agree the original was not great, either, but the \"0000000\"\nlooked like a bug to me. Hey, at least we didn't segfault! :)\n\n> Since we have an API that is explicitly about creating fake merge base\n> commits in merge_recursive_generic(), we should provide a better label\n> when using that API with one merge base.  So, when\n> merge_recursive_generic() is called with one merge base, set the label\n> to:\n> \n>          \"constructed merge base\"\n\nThat makes perfect sense to me. Thanks for the quick turnaround.\n\n> Changes since v1:\n>   - We only had a problem if the number of fake merge bases was exactly\n>     one; update the patch to check for that and update the commit message\n>     accordingly.\n\nThat makes sense. We'd still want to say \"merged common ancestors\" even\nif one of those ancestors was fake.\n\n>  merge-recursive.c | 7 ++++++-\n>  1 file changed, 6 insertions(+), 1 deletion(-)\n\nThe patch itself looks good to me (though admittedly I'm not too\nfamiliar with this area).\n\nMaybe worth squashing in this test?\n\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 3fb68e0aae..860542aad0 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,4 +186,17 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n+test_expect_success 'rebase describes fake ancestor base' '\n+\ttest_create_repo rebase &&\n+\t(\n+\t\tcd rebase &&\n+\t\ttest_commit base file &&\n+\t\ttest_commit master file &&\n+\t\tgit checkout -b side HEAD^ &&\n+\t\ttest_commit side file &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\tgrep \"||||||| constructed merge base\" file\n+\t)\n+'\n+\n test_done\n\n-Peff\n"},{"id":"383224","messageId":"20191001181727.16009-1-newren@gmail.com","threadId":"51526","inReplyTo":"20191001144940.GH10875@sigill.intra.peff.net","subject":"[PATCH v3] merge-recursive: fix the diff3 common ancestor label for virtual commits","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-10-01T18:17:27Z","receivedAt":"2019-10-01T18:17:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 743474cbfa8b (\"merge-recursive: provide a better label for\ndiff3 common ancestor\", 2019-08-17), the label for the common ancestor\nwas changed from always being\n\n         \"merged common ancestors\"\n\nto instead be based on the number of merge bases:\n\n    >=2: \"merged common ancestors\"\n      1: <abbreviated commit hash>\n      0: \"<empty tree>\"\n\nUnfortunately, this did not take into account that when we have a single\nmerge base, that merge base could be fake or constructed.  In such\ncases, this resulted in a label of \"00000000\".  Of course, the previous\nlabel of \"merged common ancestors\" was also misleading for this case.\nSince we have an API that is explicitly about creating fake merge base\ncommits in merge_recursive_generic(), we should provide a better label\nwhen using that API with one merge base.  So, when\nmerge_recursive_generic() is called with one merge base, set the label\nto:\n\n         \"constructed merge base\"\n\nNote that callers of merge_recursive_generic() include the builtin\ncommands git-am (in combination with git apply --build-fake-ancestor),\ngit-merge-recursive, and git-stash.\n\nHelped-by: Jeff King <peff@peff.net>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nNote: Applies to the top of en/merge-recursive-cleanup, which is in next.\n\nChanges since v2:\n  - Squashed in the testcase Peff provided and changed his attribution\n    from Reported-by to Helped-by due to the testcase\n\nRange-diff:\n1:  3f4444bfd7 ! 1:  208e69a4eb merge-recursive: fix the diff3 common ancestor label for virtual commits\n    @@ Commit message\n         commands git-am (in combination with git apply --build-fake-ancestor),\n         git-merge-recursive, and git-stash.\n     \n    -    Reported-by: Jeff King <peff@peff.net>\n    +    Helped-by: Jeff King <peff@peff.net>\n         Signed-off-by: Elijah Newren <newren@gmail.com>\n     \n      ## merge-recursive.c ##\n    @@ merge-recursive.c: int merge_recursive_generic(struct merge_options *opt,\n      \t}\n      \n      \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\n    +\n    + ## t/t6047-diff3-conflict-markers.sh ##\n    +@@ t/t6047-diff3-conflict-markers.sh: test_expect_success 'check multiple merge bases' '\n    + \t)\n    + '\n    + \n    ++test_expect_success 'rebase describes fake ancestor base' '\n    ++\ttest_create_repo rebase &&\n    ++\t(\n    ++\t\tcd rebase &&\n    ++\t\ttest_commit base file &&\n    ++\t\ttest_commit master file &&\n    ++\t\tgit checkout -b side HEAD^ &&\n    ++\t\ttest_commit side file &&\n    ++\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n    ++\t\tgrep \"||||||| constructed merge base\" file\n    ++\t)\n    ++'\n    ++\n    + test_done\n\n merge-recursive.c                 |  7 ++++++-\n t/t6047-diff3-conflict-markers.sh | 13 +++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b058741f00..e12d91f48a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3550,6 +3550,8 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n \t\t\t\t\t\t\t \"ancestor\");\n \t\tancestor_name = \"empty tree\";\n+\t} else if (opt->ancestor) {\n+\t\tancestor_name = opt->ancestor;\n \t} else if (merge_bases) {\n \t\tancestor_name = \"merged common ancestors\";\n \t} else {\n@@ -3689,7 +3691,8 @@ int merge_recursive(struct merge_options *opt,\n {\n \tint clean;\n \n-\tassert(opt->ancestor == NULL);\n+\tassert(opt->ancestor == NULL ||\n+\t       !strcmp(opt->ancestor, \"constructed merge base\"));\n \n \tif (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))\n \t\treturn -1;\n@@ -3741,6 +3744,8 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t\t\t   oid_to_hex(merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n+\t\tif (num_merge_bases == 1)\n+\t\t\topt->ancestor = \"constructed merge base\";\n \t}\n \n \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\ndiff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh\nindex 3fb68e0aae..860542aad0 100755\n--- a/t/t6047-diff3-conflict-markers.sh\n+++ b/t/t6047-diff3-conflict-markers.sh\n@@ -186,4 +186,17 @@ test_expect_success 'check multiple merge bases' '\n \t)\n '\n \n+test_expect_success 'rebase describes fake ancestor base' '\n+\ttest_create_repo rebase &&\n+\t(\n+\t\tcd rebase &&\n+\t\ttest_commit base file &&\n+\t\ttest_commit master file &&\n+\t\tgit checkout -b side HEAD^ &&\n+\t\ttest_commit side file &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 rebase master &&\n+\t\tgrep \"||||||| constructed merge base\" file\n+\t)\n+'\n+\n test_done\n-- \n2.23.0.25.g3f4444bfd7.dirty\n\n"},{"id":"383558","messageId":"xmqq7e5h46an.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20191001181727.16009-1-newren@gmail.com","subject":"Re: [PATCH v3] merge-recursive: fix the diff3 common ancestor label for virtual commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-07T02:51:12Z","receivedAt":"2019-10-07T02:51:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> In commit 743474cbfa8b (\"merge-recursive: provide a better label for\n> diff3 common ancestor\", 2019-08-17), the label for the common ancestor\n> was changed from always being\n>\n>          \"merged common ancestors\"\n>\n> to instead be based on the number of merge bases:\n>\n>     >=2: \"merged common ancestors\"\n>       1: <abbreviated commit hash>\n>       0: \"<empty tree>\"\n\nI just saw, while running \"git checkout next && git merge master\",\nthis:\n\n    diff --git a/packfile.c b/packfile.c\n    index 3220761fb4..6413693a17 100644\n    --- a/packfile.c\n    +++ b/packfile.c\n    @@ -16,7 +16,22 @@\n     #include \"object-store.h\"\n     #include \"midx.h\"\n     #include \"commit-graph.h\"\n    +<<<<<<< HEAD\n     #include \"promisor-remote.h\"\n    +||||||| merged common ancestors\n    +<<<<<<<<< Temporary merge branch 1\n    +||||||||| 瀉3.\n    +=========\n    +#include \"promisor-remote.h\"\n    +>>>>>>>>> Temporary merge branch 2\n    +||||||||| 瀉3.\n    +>>>>>>>>>>> Temporary merge branch 2\n    +=========\n    +#include \"promisor-remote.h\"\n    +>>>>>>>>> Temporary merge branch 2\n    +=======\n    +#include \"promisor-remote.h\"\n    +>>>>>>> master\n\n     char *odb_pack_name(struct strbuf *buf,\n                        const unsigned char *hash,\n\nwhere the common ancestor blocks are marked with random garbage.\n\nI do not have time or patience to bisect and do not know if this is\na regression around this commit, but it smells suspiciously close.\n\n"},{"id":"383579","messageId":"20191007155211.23067-1-newren@gmail.com","threadId":"51526","inReplyTo":"xmqq7e5h46an.fsf@gitster-ct.c.googlers.com","subject":"[PATCH] merge-recursive: fix the fix to the diff3 common ancestor label","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-10-07T15:52:11Z","receivedAt":"2019-10-07T15:52:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In commit 208e69a4ebce (\"merge-recursive: fix the diff3 common ancestor\nlabel for virtual commits\", 2019-09-30) which was a fix to commit\n743474cbfa8b (\"merge-recursive: provide a better label for diff3 common\nancestor\", 2019-08-17), the label for the common ancestor was changed\nfrom always being\n\n         \"merged common ancestors\"\n\nto instead be based on the number of merge bases and whether the merge\nbase was a real commit or a virtual one:\n\n    >=2: \"merged common ancestors\"\n      1, via merge_recursive_generic: \"constructed merge base\"\n      1, otherwise: <abbreviated commit hash>\n      0: \"<empty tree>\"\n\nThe handling for \"constructed merge base\" worked by allowing\nopt->ancestor to be set in merge_recursive_generic(), so we payed\nattention to the setting of that variable in merge_recursive_internal().\nNow, for the outer merge, the code flow was simply the following:\n\n\tancestor_name = \"merged merge bases\"\n\tloop over merge_bases: merge_recursive_internal()\n\nThe first merge base not needing recursion would determine its own\nancestor_name however necessary and thus run\n\n\tancestor_name = $SOMETHING\n\tempty loop over merge_bases...\n\topt->ancestor = ancestor_name\n        merge_trees_internal()\n\nNow, the next set of merge_bases that would need to be merged after this\nparticular merge had completed would note that opt->ancestor has been\nset to something (to a local ancestor_name variable that has since been\npopped off the stack), and thus it would run:\n\n\t... else if (opt->ancestor) {\n\t\tancestor_name = opt->ancestor;  /* OOPS! */\n        loop over merge_bases: merge_recursive_internal()\n        opt->ancestor = ancestor_name\n        merge_trees_internal()\n\nThis resulted in garbage strings being printed for the virtual merge\nbases, which was visible in git.git by just merging commit b744c3af07\ninto commit 6d8cb22a4f.  There are two ways to fix this: set\nopt->ancestor to NULL after using it to avoid re-use, or add a\n!opt->priv->call_depth check to the if block for using a pre-defined\nopt->ancestor.  Apply both fixes.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e12d91f48a..2653ba9a50 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3550,7 +3550,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n \t\t\t\t\t\t\t \"ancestor\");\n \t\tancestor_name = \"empty tree\";\n-\t} else if (opt->ancestor) {\n+\t} else if (opt->ancestor && !opt->priv->call_depth) {\n \t\tancestor_name = opt->ancestor;\n \t} else if (merge_bases) {\n \t\tancestor_name = \"merged common ancestors\";\n@@ -3600,6 +3600,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t\t\t\t  merged_merge_bases),\n \t\t\t\t     &result_tree);\n \tstrbuf_release(&merge_base_abbrev);\n+\topt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */\n \tif (clean < 0) {\n \t\tflush_output(opt);\n \t\treturn clean;\n-- \n2.23.0.26.gfc82117b87\n\n"},{"id":"383642","messageId":"xmqqv9t0nf0t.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20191007155211.23067-1-newren@gmail.com","subject":"Re: [PATCH] merge-recursive: fix the fix to the diff3 common ancestor label","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-08T02:32:18Z","receivedAt":"2019-10-08T02:32:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> This resulted in garbage strings being printed for the virtual merge\n> bases, which was visible in git.git by just merging commit b744c3af07\n> into commit 6d8cb22a4f.  There are two ways to fix this: set\n> opt->ancestor to NULL after using it to avoid re-use, or add a\n> !opt->priv->call_depth check to the if block for using a pre-defined\n> opt->ancestor.  Apply both fixes.\n\nThanks for quickly fixing this.  Will apply.\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 3 ++-\n>  1 file changed, 2 insertions(+), 1 deletion(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index e12d91f48a..2653ba9a50 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3550,7 +3550,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \t\tmerged_merge_bases = make_virtual_commit(opt->repo, tree,\n>  \t\t\t\t\t\t\t \"ancestor\");\n>  \t\tancestor_name = \"empty tree\";\n> -\t} else if (opt->ancestor) {\n> +\t} else if (opt->ancestor && !opt->priv->call_depth) {\n>  \t\tancestor_name = opt->ancestor;\n>  \t} else if (merge_bases) {\n>  \t\tancestor_name = \"merged common ancestors\";\n> @@ -3600,6 +3600,7 @@ static int merge_recursive_internal(struct merge_options *opt,\n>  \t\t\t\t\t\t\t  merged_merge_bases),\n>  \t\t\t\t     &result_tree);\n>  \tstrbuf_release(&merge_base_abbrev);\n> +\topt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */\n>  \tif (clean < 0) {\n>  \t\tflush_output(opt);\n>  \t\treturn clean;\n"},{"id":"383643","messageId":"xmqqr23oneui.fsf@gitster-ct.c.googlers.com","threadId":"51526","inReplyTo":"20191007155211.23067-1-newren@gmail.com","subject":"Re: [PATCH] merge-recursive: fix the fix to the diff3 common ancestor label","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-10-08T02:36:05Z","receivedAt":"2019-10-08T02:36:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> In commit 208e69a4ebce (\"merge-recursive: fix the diff3 common ancestor\n\nI think the above was an earlier incarntion of what is now known as\n8e4ec337 (\"merge-recursive: fix the diff3 common ancestor label for\nvirtual commits\", 2019-10-01).\n\n> label for virtual commits\", 2019-09-30) which was a fix to commit\n> 743474cbfa8b (\"merge-recursive: provide a better label for diff3 common\n> ...\n> The handling for \"constructed merge base\" worked by allowing\n> opt->ancestor to be set in merge_recursive_generic(), so we payed\n\ns/payed/paid/\n\n> attention to the setting of that variable in merge_recursive_internal().\n\nThanks, again.\n"},{"id":"383687","messageId":"CABPp-BG-h3yDeDQqa78WisnXNzqsgmBx7T_MS5LbSx5an-FQLg@mail.gmail.com","threadId":"51526","inReplyTo":"xmqqr23oneui.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH] merge-recursive: fix the fix to the diff3 common ancestor label","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2019-10-08T16:16:47Z","receivedAt":"2019-10-08T16:17:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 7, 2019 at 7:36 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > In commit 208e69a4ebce (\"merge-recursive: fix the diff3 common ancestor\n>\n> I think the above was an earlier incarntion of what is now known as\n> 8e4ec337 (\"merge-recursive: fix the diff3 common ancestor label for\n> virtual commits\", 2019-10-01).\n\nOops, yes.\n\n> > label for virtual commits\", 2019-09-30) which was a fix to commit\n> > 743474cbfa8b (\"merge-recursive: provide a better label for diff3 common\n> > ...\n> > The handling for \"constructed merge base\" worked by allowing\n> > opt->ancestor to be set in merge_recursive_generic(), so we payed\n>\n> s/payed/paid/\n\nUgh, two simple mistakes in the commit message.  I see you've not only\nproofread the commit but fixed up the commit message for me in pu;\nthanks.\n\nElijah\n"}]}