{"thread":{"id":"57288","subject":"[PATCH 01/12] merge-tree: rename merge_trees() to trivial_merge_trees()","startedAt":"2022-01-22T21:56:17Z","lastAt":"2022-06-18T21:58:21Z","messageCount":240,"participants":["Elijah Newren via GitGitGadget","René Scharfe","Ævar Arnfjörð Bjarmason","Elijah Newren","Johannes Schindelin","Christian Couder","Johannes Sixt","Johannes Schindelin via GitGitGadget","Junio C Hamano","Johannes Altmanninger","Josh Steadmon","Emily Shaffer"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"446699","messageId":"4a7cd5542bb2f89b4874e4115542ccee9c4639af.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 01/12] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:51Z","receivedAt":"2022-01-22T21:56:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"446700","messageId":"4780ff6784d426bf0a96859ef9bf9c14e87d5f50.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 02/12] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:52Z","receivedAt":"2022-01-22T21:56:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"446701","messageId":"pull.1122.git.1642888562.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":null,"subject":"[PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:50Z","receivedAt":"2022-01-22T21:56:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Note1: Depends on en/remerge-diff (but no need to pick it up; it's still\nRFC).\n\n(Note2: This is a continuation of my series at [0], but I can't change the\nbase repository for a pull request in GitHub so I had to open a new PR and\nthat makes it look like a new series. [0]\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/)\n\n== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs.\n\n== Updates Log ==\n\nUpdates since v2 (thanks to Christian, Dscho, Ramsay, and René for\nsuggestions and comments on v2):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since v1 (thanks to Johannes Altmanninger and Fabian for suggestions\non v1):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\n== Other notes ==\n\nStuff intentionally NOT included, but which others seemed to feel strongly\nabout; they'd need to convince me more on these:\n\n * Any form of diff output[1]\n * A way to omit printing the generated tree hash[2][3] In regards to these,\n   also see also some of the new info in Documentation/git-merge-tree.txt,\n   namely the expanded paragraph on \"the second form is deprecated\" in the\n   description as regards diff output usability and performance, and also\n   the final paragraph of the new \"Mistakes to avoid\" section in regards to\n   tree hash.\n\n[1]\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2201101427440.339@tvgsbejvaqbjf.bet/\n(section starting with \"Providing a tree\") [2]\nhttps://lore.kernel.org/git/CABPp-BHvXrP0sTTmuTYfACoJTCcm9+wk_f441nj4TstrmQdqMQ@mail.gmail.com/\n(sections starting with \"avoid printing\" and \"Where did I suggest\") [3]\nhttps://lore.kernel.org/git/CABPp-BGdbh=HM7jA+_RTwSWVcMr_qvEY7RoNXooeBT2NB4Ubzw@mail.gmail.com/\n(section starting with \"Providing a tree object\")\n\nElijah Newren (12):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-ort: allow update messages to be written to different file\n    stream\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\n Documentation/git-merge-tree.txt | 182 +++++++++++++++++++++++++++++--\n builtin/merge-tree.c             | 178 ++++++++++++++++++++++++++++--\n git.c                            |   2 +-\n merge-ort.c                      | 109 ++++++++++++------\n merge-ort.h                      |  30 +++++\n t/t4301-merge-tree-real.sh       | 163 +++++++++++++++++++++++++++\n 6 files changed, 603 insertions(+), 61 deletions(-)\n create mode 100755 t/t4301-merge-tree-real.sh\n\n\nbase-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n-- \ngitgitgadget\n"},{"id":"446702","messageId":"65fdae9ddba7c7065ce27acbf4e80a1a74842aa7.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:53Z","receivedAt":"2022-01-22T21:56:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 67 ++++++++++++++++++++++++++++++++++++++------\n git.c                |  2 +-\n 2 files changed, 59 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..33e47cc1534 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,57 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+struct merge_tree_options {\n+\tint real;\n+\tint trivial;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_BOOL(0, \"write-tree\", &o.real,\n+\t\t\t N_(\"do a real merge instead of a trivial merge\")),\n+\t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n+\t\t\t N_(\"do a trivial merge only\")),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Check for a request for basic help */\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n+\t\tusage_with_options(merge_tree_usage, mt_options);\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, 0);\n+\tif (o.real && o.trivial)\n+\t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n+\tif (o.real || o.trivial) {\n+\t\texpected_remaining_argc = (o.real ? 2 : 3);\n+\t\tif (argc != expected_remaining_argc)\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t} else {\n+\t\tif (argc < 2 || argc > 3)\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\to.real = (argc == 2);\n+\t}\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.real)\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..6090a1289db 100644\n--- a/git.c\n+++ b/git.c\n@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"446704","messageId":"05bd17686e1404c81542b6bbf69dcd3decb83c5b.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 04/12] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:54Z","receivedAt":"2022-01-22T21:56:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean) or 1 (conflicts present)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 74 ++++++++++++++++++++++-----\n builtin/merge-tree.c             | 55 +++++++++++++++++++-\n t/t4301-merge-tree-real.sh       | 87 ++++++++++++++++++++++++++++++++\n 3 files changed, 203 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-real.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..b900bc1362c 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,76 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>\n \n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The first form will merge the two branches, doing a full recursive\n+merge with rename detection.  The rest of this manual (other than the\n+next paragraph) describes the first form in more detail -- including\n+options, output format, exit status, and usage notes.\n+\n+The second form is deprecated; it is kept for backward compatibility\n+reasons but may be deleted in the future.  It will only do a trivial\n+merge.  It reads three tree-ish, and outputs trivial merge results and\n+conflicting stages to the standard output in a semi-diff format.\n+Since this was designed for higher level scripts to consume and merge\n+the results back into the index, it omits entries that match\n+<branch1>.  The result of this second form is is similar to what\n+three-way 'git read-tree -m' does, but instead of storing the results\n+in the index, the command outputs the entries to the standard output.\n+This form not only has limited applicability, the output format is\n+also difficult to work with, and it will generally be less performant\n+than the first form even on successful merges (especially if working\n+in large repositories).  The remainder of this manual will only\n+discuss the first form.\n+\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1.\n+\n+USAGE NOTES\n+-----------\n+\n+git-merge-tree was written to be low-level plumbing, similar to\n+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n+be used as a part of a series of steps such as\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n+\n+However, it does not quite fit into the same category of low-level\n+plumbing commands since the possibility of merge conflicts give it a\n+much higher chance of the command not succeeding.\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 33e47cc1534..0c19639594d 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -393,7 +396,57 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *common;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct commit_list *j;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\t/*\n+\t * TODO: Support subtree and other -X options?\n+\tif (use_strategies_nr == 1 &&\n+\t    !strcmp(use_strategies[0]->name, \"subtree\"))\n+\t\topt.subtree_shift = \"\";\n+\tfor (x = 0; x < xopts_nr; x++)\n+\t\tif (parse_merge_opt(&opt, xopts[x]))\n+\t\t\tdie(_(\"Unknown strategy option: -X%s\"), xopts[x]);\n+\t*/\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */\n+\topt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tcommon = get_merge_bases(parent1, parent2);\n+\tif (!common)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\telse if (!result.clean)\n+\t\tprintf(_(\"Conflicts!\\n\"));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\nnew file mode 100755\nindex 00000000000..e03688515c5\n--- /dev/null\n+++ b/t/t4301-merge-tree-real.sh\n@@ -0,0 +1,87 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+test \"${GIT_TEST_MERGE_ALGORITHM:-ort}\" = ort || {\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+}\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(cat .git/AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"446703","messageId":"095aa266c2bfdda47ed722fbc5a0d9c94132fbf1.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:55Z","receivedAt":"2022-01-22T21:56:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNo functional changes included in this patch; it's just a preparatory\nstep to allow the printed messages to be handled differently by other\ncallers, such as in `git merge-tree --write-tree`.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 77 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 36 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9bf15a01db8..f9e35b0f96b 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4273,42 +4312,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n \n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"446705","messageId":"2f296aeeefbf8340cfb8b7fa4fef5ad49c8b4aa1.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 07/12] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:57Z","receivedAt":"2022-01-22T21:56:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, (\"Auto-merging <PATH>\", \"CONFLICT (content):\n...\", \"CONFLICT (file/directory)\", etc.)  In fact, when non-content\nconflicts occur (such as file/directory, modify/delete, add/add with\ndiffering modes, rename/rename (1to2), etc.), these informational\nmessages are often the only notification since these conflicts are not\nrepresentable in the contents of the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----\n builtin/merge-tree.c             | 24 +++++++++++++----\n t/t4301-merge-tree-real.sh       | 21 +++++++++++++++\n 3 files changed, 78 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex b900bc1362c..fd7a867de60 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>\n \n DESCRIPTION\n@@ -38,17 +38,47 @@ than the first form even on successful merges (especially if working\n in large repositories).  The remainder of this manual will only\n discuss the first form.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+By default, for a successful merge, the output from git-merge-tree is\n+simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n+\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -72,7 +102,8 @@ be used as a part of a series of steps such as\n \n However, it does not quite fit into the same category of low-level\n plumbing commands since the possibility of merge conflicts give it a\n-much higher chance of the command not succeeding.\n+much higher chance of the command not succeeding (and NEWTREE containing\n+a bunch of stuff other than just a toplevel tree).\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 0c19639594d..560640ad911 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -391,6 +391,7 @@ static int trivial_merge(const char *base,\n struct merge_tree_options {\n \tint real;\n \tint trivial;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,\n \t\tcommit_list_insert(j->item, &merge_bases);\n \n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n-\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n+\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n-\telse if (!result.clean)\n-\t\tprintf(_(\"Conflicts!\\n\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n+\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result, stdout);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -464,6 +473,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"do a real merge instead of a trivial merge\")),\n \t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n \t\t\t N_(\"do a trivial merge only\")),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n@@ -472,10 +483,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(merge_tree_usage, mt_options);\n \n \t/* Parse arguments */\n+\toriginal_argc = argc;\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, 0);\n \tif (o.real && o.trivial)\n \t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n+\tif (!o.real && original_argc < argc)\n+\t\tdie(_(\"--write-tree must be specified if any other options are\"));\n \tif (o.real || o.trivial) {\n \t\texpected_remaining_argc = (o.real ? 2 : 3);\n \t\tif (argc != expected_remaining_argc)\ndiff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\nindex e03688515c5..c34f8e6c1ed 100755\n--- a/t/t4301-merge-tree-real.sh\n+++ b/t/t4301-merge-tree-real.sh\n@@ -84,4 +84,25 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"446706","messageId":"e3ef17eb46fdfd759030761ab6d7c35fbf24ee0f.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 06/12] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:56Z","receivedAt":"2022-01-22T21:56:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis modifies the new display_update_messages() function to allow\nprinting to somewhere other than stdout.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 7 ++++---\n merge-ort.h | 3 ++-\n 2 files changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex f9e35b0f96b..b78dde55ad9 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n }\n \n void merge_display_update_messages(struct merge_options *opt,\n-\t\t\t\t   struct merge_result *result)\n+\t\t\t\t   struct merge_result *result,\n+\t\t\t\t   FILE *stream)\n {\n \tstruct merge_options_internal *opti = result->priv;\n \tstruct hashmap_iter iter;\n@@ -4263,7 +4264,7 @@ void merge_display_update_messages(struct merge_options *opt,\n \tfor (i = 0; i < olist.nr; ++i) {\n \t\tstruct strbuf *sb = olist.items[i].util;\n \n-\t\tprintf(\"%s\", sb->buf);\n+\t\tfprintf(stream, \"%s\", sb->buf);\n \t}\n \tstring_list_clear(&olist, 0);\n \n@@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,\n \t}\n \n \tif (display_update_msgs)\n-\t\tmerge_display_update_messages(opt, result);\n+\t\tmerge_display_update_messages(opt, result, stdout);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..d643b47cb7c 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,\n  * so only call this when bypassing merge_switch_to_result().\n  */\n void merge_display_update_messages(struct merge_options *opt,\n-\t\t\t\t   struct merge_result *result);\n+\t\t\t\t   struct merge_result *result,\n+\t\t\t\t   FILE *stream);\n \n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n-- \ngitgitgadget\n\n"},{"id":"446707","messageId":"050add3e4986c457cd467b36eb4fd1f215b7406d.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 10/12] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:56:00Z","receivedAt":"2022-01-22T21:56:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide an --exclude-oids-and-modes option for users to exclude the\nmode, oid, and stage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 30 ++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-real.sh       | 26 ++++++++++++++++++++++++--\n 3 files changed, 58 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 041a4ac2785..beb08269a70 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -41,6 +41,11 @@ discuss the first form.\n OPTIONS\n -------\n \n+--exclude-oids-and-modes::\n+\tInstead of writing a list of (mode, oid, stage, path) tuples\n+\tto output for conflicted files, just provide a list of\n+\tfilenames with conflicts.\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -58,7 +63,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -70,18 +75,23 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--exclude-oids-and-modes` option is passed, the mode, object, and\n+stage will be omitted.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -113,6 +123,14 @@ plumbing commands since the possibility of merge conflicts give it a\n much higher chance of the command not succeeding (and NEWTREE containing\n a bunch of stuff other than just a toplevel tree).\n \n+git-merge-tree was written to provide users with the same information\n+that they'd have access to if using `git merge`:\n+  * what would be written to the working tree (the <OID of toplevel tree>)\n+  * the higher order stages that would be written to the index (the\n+    <Conflicted file info>)\n+  * any messages that would have been printed to stdout (the <Informational\n+    messages>)\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex d8eeeb3f306..7aa7f9fd54a 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -395,6 +395,7 @@ struct merge_tree_options {\n \tint real;\n \tint trivial;\n \tint show_messages;\n+\tint exclude_oids_and_modes;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -461,7 +462,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->exclude_oids_and_modes)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -495,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"do a trivial merge only\")),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F(0, \"exclude-oids-and-modes\",\n+\t\t\t   &o.exclude_oids_and_modes,\n+\t\t\t   N_(\"list conflicted files without oids and modes\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\nindex 43c9950dedb..e921115cd2a 100755\n--- a/t/t4301-merge-tree-real.sh\n+++ b/t/t4301-merge-tree-real.sh\n@@ -46,6 +46,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(cat .git/AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -85,7 +86,7 @@ test_expect_success 'Barf on too many arguments' '\n '\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --exclude-oids-and-modes side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \t# Expected results:\n@@ -108,7 +109,7 @@ test_expect_success 'test conflict notices and such' '\n '\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-oids-and-modes side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -116,4 +117,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"446708","messageId":"35e0ed9271a0229fe2acd2385a7e4171d4dfe077.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:58Z","receivedAt":"2022-01-22T21:56:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u` -- conflicted\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex b78dde55ad9..5e7cea6cc8f 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex d643b47cb7c..e635a294ea8 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result,\n \t\t\t\t   FILE *stream);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"446709","messageId":"ba8a50f03cba3f3fcd734be5a75b6657f8f8029d.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 11/12] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:56:01Z","receivedAt":"2022-01-22T21:56:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-real.sh       | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex beb08269a70..df10a5963c7 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -52,6 +52,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n OUTPUT\n ------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 7aa7f9fd54a..98441d5e05b 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -394,6 +394,7 @@ static int trivial_merge(const char *base,\n struct merge_tree_options {\n \tint real;\n \tint trivial;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint exclude_oids_and_modes;\n };\n@@ -440,7 +441,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common)\n+\tif (!common && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tfor (j = common; j; j = j->next)\n \t\tcommit_list_insert(j->item, &merge_bases);\n@@ -504,6 +505,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.exclude_oids_and_modes,\n \t\t\t   N_(\"list conflicted files without oids and modes\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\nindex e921115cd2a..a0447410655 100755\n--- a/t/t4301-merge-tree-real.sh\n+++ b/t/t4301-merge-tree-real.sh\n@@ -37,7 +37,13 @@ test_expect_success setup '\n \t>whatever/empty &&\n \tgit add numbers greeting whatever/empty &&\n \ttest_tick &&\n-\tgit commit -m other-modifications\n+\tgit commit -m other-modifications &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Content merge and a few conflicts' '\n@@ -138,4 +144,20 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"446710","messageId":"4123209cafc953939be66c17e0560749770c5ecc.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 12/12] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:56:02Z","receivedAt":"2022-01-22T21:56:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex df10a5963c7..3c1aa0ffbae 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -136,6 +136,52 @@ that they'd have access to if using `git merge`:\n   * any messages that would have been printed to stdout (the <Informational\n     messages>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <Conflicted file info> section instead.  Not\n+only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n+check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <Conflicted file info> list.  The information there is insufficient\n+to do so.  For example: Rename/rename(1to2) conflicts (both sides\n+renamed the same file differently) will result in three different file\n+having higher order stages (but each only has one higher order stage),\n+with no way (short of the <Informational messages> section) to determine\n+which three files are related.  File/directory conflicts also result in\n+a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<Informational messages> section has the necessary info, though it is\n+not designed to be machine parseable.\n+\n+Do NOT assume all filenames listed in the <Informational messages>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <Conflicted file info> and re-merging\n+them to present the conflicts to the user.  This will lose information.\n+Instead, look up the version of the file found within the <OID of\n+toplevel tree> and show that instead.  In particular, the latter will\n+have conflict markers annotated with the original branch/commit being\n+merged and, if renames were involved, the original filename.  While you\n+could include the original branch/commit in the conflict marker\n+annotations when re-merging, the original filename is not available from\n+the <Conflicted file info> and thus you would be losing information that\n+might help the user resolve the conflict.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n"},{"id":"446711","messageId":"fcbb087fa8865ac05e20473d822cd9795590ee38.1642888562.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T21:55:59Z","receivedAt":"2022-01-22T21:56:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  8 ++++++++\n builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n t/t4301-merge-tree-real.sh       | 11 +++++++++++\n 3 files changed, 41 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex fd7a867de60..041a4ac2785 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -58,6 +58,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -69,6 +70,13 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 560640ad911..d8eeeb3f306 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -395,7 +398,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *common;\n@@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n \t\tprintf(\"\\n\");\n \t\tmerge_display_update_messages(&opt, &result, stdout);\n@@ -502,7 +522,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.real)\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\nindex c34f8e6c1ed..43c9950dedb 100755\n--- a/t/t4301-merge-tree-real.sh\n+++ b/t/t4301-merge-tree-real.sh\n@@ -94,6 +94,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -105,4 +107,13 @@ test_expect_success 'test conflict notices and such' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"446715","messageId":"90488a50-c015-c9c8-e58b-81ccb66feaf6@web.de","threadId":"57288","inReplyTo":"65fdae9ddba7c7065ce27acbf4e80a1a74842aa7.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-23T08:05:01Z","receivedAt":"2022-01-23T08:05:18Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 22.01.22 um 22:55 schrieb Elijah Newren via GitGitGadget:\n> From: Elijah Newren <newren@gmail.com>\n>\n> Let merge-tree accept a `--write-tree` parameter for choosing real\n> merges instead of trivial merges, and accept an optional\n> `--trivial-merge` option to get the traditional behavior.  Note that\n> these accept different numbers of arguments, though, so these names\n> need not actually be used.\n>\n> Note that real merges differ from trivial merges in that they handle:\n>   - three way content merges\n>   - recursive ancestor consolidation\n>   - renames\n>   - proper directory/file conflict handling\n>   - etc.\n> Basically all the stuff you'd expect from `git merge`, just without\n> updating the index and working tree.  The initial shell added here does\n> nothing more than die with \"real merges are not yet implemented\", but\n> that will be fixed in subsequent commits.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  builtin/merge-tree.c | 67 ++++++++++++++++++++++++++++++++++++++------\n>  git.c                |  2 +-\n>  2 files changed, 59 insertions(+), 10 deletions(-)\n>\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 914ec960b7e..33e47cc1534 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -3,13 +3,12 @@\n>  #include \"tree-walk.h\"\n>  #include \"xdiff-interface.h\"\n>  #include \"object-store.h\"\n> +#include \"parse-options.h\"\n>  #include \"repository.h\"\n>  #include \"blob.h\"\n>  #include \"exec-cmd.h\"\n>  #include \"merge-blobs.h\"\n>\n> -static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n> -\n>  struct merge_list {\n>  \tstruct merge_list *next;\n>  \tstruct merge_list *link;\t/* other stages for this object */\n> @@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n>  \treturn buf;\n>  }\n>\n> -static int trivial_merge(int argc, const char **argv)\n> +static int trivial_merge(const char *base,\n> +\t\t\t const char *branch1,\n> +\t\t\t const char *branch2)\n>  {\n>  \tstruct repository *r = the_repository;\n>  \tstruct tree_desc t[3];\n>  \tvoid *buf1, *buf2, *buf3;\n>\n> -\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n> -\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n> -\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n> +\tbuf1 = get_tree_descriptor(r, t+0, base);\n> +\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n> +\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n>  \ttrivial_merge_trees(t, \"\");\n>  \tfree(buf1);\n>  \tfree(buf2);\n> @@ -384,9 +385,57 @@ static int trivial_merge(int argc, const char **argv)\n>  \treturn 0;\n>  }\n>\n> +struct merge_tree_options {\n> +\tint real;\n> +\tint trivial;\n> +};\n> +\n> +static int real_merge(struct merge_tree_options *o,\n> +\t\t      const char *branch1, const char *branch2)\n> +{\n> +\tdie(_(\"real merges are not yet implemented\"));\n> +}\n> +\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -\tif (argc != 4)\n> -\t\tusage(merge_tree_usage);\n> -\treturn trivial_merge(argc, argv);\n> +\tstruct merge_tree_options o = { 0 };\n> +\tint expected_remaining_argc;\n> +\n> +\tconst char * const merge_tree_usage[] = {\n> +\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> +\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> +\t\tNULL\n> +\t};\n> +\tstruct option mt_options[] = {\n> +\t\tOPT_BOOL(0, \"write-tree\", &o.real,\n> +\t\t\t N_(\"do a real merge instead of a trivial merge\")),\n> +\t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n> +\t\t\t N_(\"do a trivial merge only\")),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\t/* Check for a request for basic help */\n> +\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n> +\t\tusage_with_options(merge_tree_usage, mt_options);\n\nThis is unnecessary; parse_options() handles -h already.\n\n> +\n> +\t/* Parse arguments */\n> +\targc = parse_options(argc, argv, prefix, mt_options,\n> +\t\t\t     merge_tree_usage, 0);\n> +\tif (o.real && o.trivial)\n> +\t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n\n12909b6b8a (i18n: turn \"options are incompatible\" into \"cannot be used\ntogether\", 2022-01-05) standardized messages of that kind; let's stick\nto that to simplify translation:\n\n\t\tdie(_(\"options '%s' and '%s' cannot be used together\"),\n\t\t    \"--write-tree\", \"--trivial-merge\");\n\n> +\tif (o.real || o.trivial) {\n> +\t\texpected_remaining_argc = (o.real ? 2 : 3);\n> +\t\tif (argc != expected_remaining_argc)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t} else {\n> +\t\tif (argc < 2 || argc > 3)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t\to.real = (argc == 2);\n> +\t}\n> +\n> +\t/* Do the relevant type of merge */\n> +\tif (o.real)\n> +\t\treturn real_merge(&o, argv[0], argv[1]);\n> +\telse\n> +\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n>  }\n> diff --git a/git.c b/git.c\n> index 5ff21be21f3..6090a1289db 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n> -\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n> +\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n>  \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n>  \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n>  \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n"},{"id":"446739","messageId":"220124.86lez5ihso.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"65fdae9ddba7c7065ce27acbf4e80a1a74842aa7.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-24T09:46:13Z","receivedAt":"2022-01-24T09:50:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Let merge-tree accept a `--write-tree` parameter for choosing real\n> merges instead of trivial merges, and accept an optional\n> `--trivial-merge` option to get the traditional behavior.  Note that\n> these accept different numbers of arguments, though, so these names\n> need not actually be used.\n>\n> Note that real merges differ from trivial merges in that they handle:\n>   - three way content merges\n>   - recursive ancestor consolidation\n>   - renames\n>   - proper directory/file conflict handling\n>   - etc.\n> Basically all the stuff you'd expect from `git merge`, just without\n> updating the index and working tree.  The initial shell added here does\n> nothing more than die with \"real merges are not yet implemented\", but\n> that will be fixed in subsequent commits.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  builtin/merge-tree.c | 67 ++++++++++++++++++++++++++++++++++++++------\n>  git.c                |  2 +-\n>  2 files changed, 59 insertions(+), 10 deletions(-)\n>\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 914ec960b7e..33e47cc1534 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -3,13 +3,12 @@\n>  #include \"tree-walk.h\"\n>  #include \"xdiff-interface.h\"\n>  #include \"object-store.h\"\n> +#include \"parse-options.h\"\n>  #include \"repository.h\"\n>  #include \"blob.h\"\n>  #include \"exec-cmd.h\"\n>  #include \"merge-blobs.h\"\n>  \n> -static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n> -\n>  struct merge_list {\n>  \tstruct merge_list *next;\n>  \tstruct merge_list *link;\t/* other stages for this object */\n> @@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n>  \treturn buf;\n>  }\n>  \n> -static int trivial_merge(int argc, const char **argv)\n> +static int trivial_merge(const char *base,\n> +\t\t\t const char *branch1,\n> +\t\t\t const char *branch2)\n>  {\n>  \tstruct repository *r = the_repository;\n>  \tstruct tree_desc t[3];\n>  \tvoid *buf1, *buf2, *buf3;\n>  \n> -\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n> -\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n> -\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n> +\tbuf1 = get_tree_descriptor(r, t+0, base);\n> +\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n> +\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n>  \ttrivial_merge_trees(t, \"\");\n>  \tfree(buf1);\n>  \tfree(buf2);\n> @@ -384,9 +385,57 @@ static int trivial_merge(int argc, const char **argv)\n>  \treturn 0;\n>  }\n>  \n> +struct merge_tree_options {\n> +\tint real;\n> +\tint trivial;\n> +};\n> +\n> +static int real_merge(struct merge_tree_options *o,\n> +\t\t      const char *branch1, const char *branch2)\n> +{\n> +\tdie(_(\"real merges are not yet implemented\"));\n> +}\n> +\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -\tif (argc != 4)\n> -\t\tusage(merge_tree_usage);\n> -\treturn trivial_merge(argc, argv);\n> +\tstruct merge_tree_options o = { 0 };\n> +\tint expected_remaining_argc;\n> +\n> +\tconst char * const merge_tree_usage[] = {\n> +\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> +\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> +\t\tNULL\n> +\t};\n> +\tstruct option mt_options[] = {\n> +\t\tOPT_BOOL(0, \"write-tree\", &o.real,\n> +\t\t\t N_(\"do a real merge instead of a trivial merge\")),\n> +\t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n> +\t\t\t N_(\"do a trivial merge only\")),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\t/* Check for a request for basic help */\n> +\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n> +\t\tusage_with_options(merge_tree_usage, mt_options);\n\nIs this bit cargo-culted from something else, perhaps\nnon-parse-options.c usage? I don't think this is needed, the\nparse_options() below intercepts \"-h\" by default.\n\n> +\t/* Parse arguments */\n> +\targc = parse_options(argc, argv, prefix, mt_options,\n> +\t\t\t     merge_tree_usage, 0);\n> +\tif (o.real && o.trivial)\n> +\t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n\nShouldn't those two just be OPT_CMDMODE()? Then you get this\nincompatibility checking for free. See 485fd2c3dae (cat-file: make\n--batch-all-objects a CMDMODE, 2021-12-28).\n\n> +\tif (o.real || o.trivial) {\n> +\t\texpected_remaining_argc = (o.real ? 2 : 3);\n> +\t\tif (argc != expected_remaining_argc)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t} else {\n> +\t\tif (argc < 2 || argc > 3)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t\to.real = (argc == 2);\n> +\t}\n\nAnd this can also be done like this, but I wonder if using\nPARSE_OPT_STOP_AT_NON_OPTION and then routing to a sub-function wouldn't\nbe better, i.e. to treat these like sub-commands if they've got\ndifferent arity etc.\n\n> +\t/* Do the relevant type of merge */\n> +\tif (o.real)\n> +\t\treturn real_merge(&o, argv[0], argv[1]);\n> +\telse\n> +\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n>  }\n> diff --git a/git.c b/git.c\n> index 5ff21be21f3..6090a1289db 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n> -\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n> +\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n>  \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n>  \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n>  \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n\n"},{"id":"446740","messageId":"220124.86h79tihjm.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"05bd17686e1404c81542b6bbf69dcd3decb83c5b.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/12] merge-tree: implement real merges","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-24T09:51:13Z","receivedAt":"2022-01-24T09:56:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n\n> +\t/*\n> +\t * TODO: Support subtree and other -X options?\n> +\tif (use_strategies_nr == 1 &&\n> +\t    !strcmp(use_strategies[0]->name, \"subtree\"))\n> +\t\topt.subtree_shift = \"\";\n> +\tfor (x = 0; x < xopts_nr; x++)\n> +\t\tif (parse_merge_opt(&opt, xopts[x]))\n\nBetter omitted WIP code in a non-RFC series?\n\n> +\t\t\tdie(_(\"Unknown strategy option: -X%s\"), xopts[x]);\n\nAs a general issue with this series, die(), BUG() etc. messages should\nstart with a non-capital letter.\n\n> +\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n\nAnd for both this...\n\n> +\t\tprintf(_(\"Conflicts!\\n\"));\n\n... and this we can just use puts(). For the former it's just less code,\nbut for the latter translators also don't need to see the always-there\n\\n in the translated message.\n\n> +# This test is ort-specific\n> +test \"${GIT_TEST_MERGE_ALGORITHM:-ort}\" = ort || {\n\nIs this ${} trickery really needed? We're not testing with \"set -u\". So just:\n\t\n\tif test \"$GIT_...\" != \"ort\"\n\tthen\n\t\t...\n\tfi\n\n> +test_expect_success 'Barf on too many arguments' '\n> +\ttest_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n> +\n> +\tgrep \"^usage: git merge-tree\" expect\n> +'\n\nNit: In most other tests these usage assertions are at the top of the\ntest, and for those we also make do with just testing the 129 exit code,\nwhich is probably enough here too...\n"},{"id":"446741","messageId":"220124.86czkhihcu.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"095aa266c2bfdda47ed722fbc5a0d9c94132fbf1.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-24T09:56:39Z","receivedAt":"2022-01-24T10:00:08Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> [...]\n> +\t/* Hack to pre-allocate olist to the desired size */\n> +\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n> +\t\t   olist.alloc);\n\nPerhaps just add a string_list_grow()? But I wonder if this is really\nneeded v.s. just using the default growing pattern here.\n\n> +\n> +\t/* Put every entry from output into olist, then sort */\n> +\tstrmap_for_each_entry(&opti->output, &iter, e) {\n> +\t\tstring_list_append(&olist, e->key)->util = e->value;\n> +\t}\n> +\tstring_list_sort(&olist);\n> +\n> +\t/* Iterate over the items, printing them */\n> +\tfor (i = 0; i < olist.nr; ++i) {\n> +\t\tstruct strbuf *sb = olist.items[i].util;\n> +\n> +\t\tprintf(\"%s\", sb->buf);\n> +\t}\n\nShorter/nicer:\n\t\n\tfor_each_string_list_item(item, &olist) {\n\t\tstruct strbuf *sb = item->util;\n\t        puts(sb->buf);\n\t}\n\t\n> -\tif (display_update_msgs) {\n> -\t\tstruct merge_options_internal *opti = result->priv;\n> -\t\tstruct hashmap_iter iter;\n> -\t\tstruct strmap_entry *e;\n> -\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n> -\t\tint i;\n> -\n> -\t\tif (opt->record_conflict_msgs_as_headers)\n> -\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n> -\n> -\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n> -\n> -\t\t/* Hack to pre-allocate olist to the desired size */\n> -\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n> -\t\t\t   olist.alloc);\n> -\n> -\t\t/* Put every entry from output into olist, then sort */\n> -\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n> -\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n> -\t\t}\n> -\t\tstring_list_sort(&olist);\n> -\n> -\t\t/* Iterate over the items, printing them */\n> -\t\tfor (i = 0; i < olist.nr; ++i) {\n> -\t\t\tstruct strbuf *sb = olist.items[i].util;\n> -\n> -\t\t\tprintf(\"%s\", sb->buf);\n> -\t\t}\n> -\t\tstring_list_clear(&olist, 0);\n\nAh, at this point I see you're just moving code around :) Sending this\nanyway in case it's useful :)\n"},{"id":"446742","messageId":"220124.868rv5ih93.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"fcbb087fa8865ac05e20473d822cd9795590ee38.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-24T10:01:28Z","receivedAt":"2022-01-24T10:02:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Callers of `git merge-tree --write-tree` will often want to know which\n> files had conflicts.  While they could potentially attempt to parse the\n> CONFLICT notices printed, those messages are not meant to be machine\n> readable.  Provide a simpler mechanism of just printing the files (in\n> the same format as `git ls-files` with quoting, but restricted to\n> [...]\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 560640ad911..d8eeeb3f306 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -11,6 +11,9 @@\n>  #include \"blob.h\"\n>  #include \"exec-cmd.h\"\n>  #include \"merge-blobs.h\"\n> +#include \"quote.h\"\n> +\n> +static int line_termination = '\\n';\n\nBut unlike ls-files we don't do anything with line_termination as a !=\n'\\n', maybe in a later commit?\n\n>  struct merge_list {\n>  \tstruct merge_list *next;\n> @@ -395,7 +398,8 @@ struct merge_tree_options {\n>  };\n>  \n>  static int real_merge(struct merge_tree_options *o,\n> -\t\t      const char *branch1, const char *branch2)\n> +\t\t      const char *branch1, const char *branch2,\n> +\t\t      const char *prefix)\n>  {\n>  \tstruct commit *parent1, *parent2;\n>  \tstruct commit_list *common;\n> @@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,\n>  \t\to->show_messages = !result.clean;\n>  \n>  \tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> +\tif (!result.clean) {\n> +\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n> +\t\tconst char *last = NULL;\n> +\t\tint i;\n> +\n> +\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n> +\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n> +\t\t\tconst char *name = conflicted_files.items[i].string;\n> +\t\t\tif (last && !strcmp(last, name))\n> +\t\t\t\tcontinue;\n> +\t\t\twrite_name_quoted_relative(\n> +\t\t\t\tname, prefix, stdout, line_termination);\n\nBut here it's never \\0 or whatever.\n"},{"id":"446743","messageId":"220124.864k5tigto.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"050add3e4986c457cd467b36eb4fd1f215b7406d.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 10/12] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-24T10:06:48Z","receivedAt":"2022-01-24T10:11:35Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Much like `git merge` updates the index with information of the form\n>     (mode, oid, stage, name)\n> provide this output for conflicted files for merge-tree as well.\n> Provide an --exclude-oids-and-modes option for users to exclude the\n> mode, oid, and stage and only get the list of conflicted filenames.\n> [...]\n> +--exclude-oids-and-modes::\n> +\tInstead of writing a list of (mode, oid, stage, path) tuples\n> +\tto output for conflicted files, just provide a list of\n> +\tfilenames with conflicts.\n> +\n> [...]\n> -This is a sequence of lines containing a filename on each line, quoted\n> -as explained for the configuration variable `core.quotePath` (see\n> -linkgit:git-config[1]).\n> +This is a sequence of lines with the format\n> +\n> +\t<mode> <object> <stage> <filename>\n> +\n> +The filename will be quoted as explained for the configuration\n> +variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n> +the `--exclude-oids-and-modes` option is passed, the mode, object, and\n> +stage will be omitted.\n>  \n>  Informational messages\n>  ~~~~~~~~~~~~~~~~~~~~~~\n>  \n>  This always starts with a blank line to separate it from the previous\n> -section, and then has free-form messages about the merge, such as:\n> +sections, and then has free-form messages about the merge, such as:\n>  \n>    * \"Auto-merging <file>\"\n>    * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n> @@ -113,6 +123,14 @@ plumbing commands since the possibility of merge conflicts give it a\n>  much higher chance of the command not succeeding (and NEWTREE containing\n>  a bunch of stuff other than just a toplevel tree).\n>  \n> +git-merge-tree was written to provide users with the same information\n> +that they'd have access to if using `git merge`:\n> +  * what would be written to the working tree (the <OID of toplevel tree>)\n> +  * the higher order stages that would be written to the index (the\n> +    <Conflicted file info>)\n> +  * any messages that would have been printed to stdout (the <Informational\n> +    messages>)\n> +\n>  GIT\n>  ---\n>  Part of the linkgit:git[1] suite\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index d8eeeb3f306..7aa7f9fd54a 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -395,6 +395,7 @@ struct merge_tree_options {\n>  \tint real;\n>  \tint trivial;\n>  \tint show_messages;\n> +\tint exclude_oids_and_modes;\n>  };\n>  \n>  static int real_merge(struct merge_tree_options *o,\n> @@ -461,7 +462,11 @@ static int real_merge(struct merge_tree_options *o,\n>  \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n>  \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n>  \t\t\tconst char *name = conflicted_files.items[i].string;\n> -\t\t\tif (last && !strcmp(last, name))\n> +\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n> +\t\t\tif (!o->exclude_oids_and_modes)\n> +\t\t\t\tprintf(\"%06o %s %d\\t\",\n> +\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n> +\t\t\telse if (last && !strcmp(last, name))\n>  \t\t\t\tcontinue;\n>  \t\t\twrite_name_quoted_relative(\n>  \t\t\t\tname, prefix, stdout, line_termination);\n> @@ -495,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  \t\t\t N_(\"do a trivial merge only\")),\n>  \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n>  \t\t\t N_(\"also show informational/conflict messages\")),\n> +\t\tOPT_BOOL_F(0, \"exclude-oids-and-modes\",\n> +\t\t\t   &o.exclude_oids_and_modes,\n> +\t\t\t   N_(\"list conflicted files without oids and modes\"),\n> +\t\t\t   PARSE_OPT_NONEG),\n>  \t\tOPT_END()\n>  \t};\n\nPerhaps this really is the last formatting information anyone will want,\nbut with a default of \"<mode> <object> <stage> <filename>\" being made\n\"<stage> <filename>\" with --exclude-oids-and-modes perhaps we'll want\n--exclude-all-except-filename etc. later.\n\nIt seems a lot simpler for new code to just support a --conflict-format\noption, lifting some code from the in-flight\nhttps://lore.kernel.org/git/db058bf670c5668fc5b95baf83667cc282cb739b.1641978175.git.dyroneteng@gmail.com/\n\nI.e. that inner loop becomes a slightly more verbose strbuf_expand(),\nbut it's all easy boilerplate code.\n\nThen we just feed \"%(objectmode) %(objectname) %(objectstage)\n%(filename)\" into it by default, and allow the user to customize it.\n"},{"id":"446754","messageId":"CABPp-BEfhpTBV1rSXjqhPeNsYUKMTMvg7JaKTPeCb-tjdo+8aw@mail.gmail.com","threadId":"57288","inReplyTo":"90488a50-c015-c9c8-e58b-81ccb66feaf6@web.de","subject":"Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-24T16:43:13Z","receivedAt":"2022-01-24T16:43:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sun, Jan 23, 2022 at 12:05 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> Am 22.01.22 um 22:55 schrieb Elijah Newren via GitGitGadget:\n...\n> > +     /* Check for a request for basic help */\n> > +     if (argc == 2 && !strcmp(argv[1], \"-h\"))\n> > +             usage_with_options(merge_tree_usage, mt_options);\n>\n> This is unnecessary; parse_options() handles -h already.\n>\n> > +\n> > +     /* Parse arguments */\n> > +     argc = parse_options(argc, argv, prefix, mt_options,\n> > +                          merge_tree_usage, 0);\n> > +     if (o.real && o.trivial)\n> > +             die(_(\"--write-tree and --trivial-merge are incompatible\"));\n>\n> 12909b6b8a (i18n: turn \"options are incompatible\" into \"cannot be used\n> together\", 2022-01-05) standardized messages of that kind; let's stick\n> to that to simplify translation:\n>\n>                 die(_(\"options '%s' and '%s' cannot be used together\"),\n>                     \"--write-tree\", \"--trivial-merge\");\n\nAh, thanks for both the pointers; will fix.\n"},{"id":"446755","messageId":"CABPp-BG1UXDLVh4_F_TQJmiM4=fFMOLFo5k27=MghbBPWPkL7A@mail.gmail.com","threadId":"57288","inReplyTo":"220124.86lez5ihso.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-24T16:54:54Z","receivedAt":"2022-01-24T16:55:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jan 24, 2022 at 1:50 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n>\n...\n> > +     /* Check for a request for basic help */\n> > +     if (argc == 2 && !strcmp(argv[1], \"-h\"))\n> > +             usage_with_options(merge_tree_usage, mt_options);\n>\n> Is this bit cargo-culted from something else, perhaps\n> non-parse-options.c usage? I don't think this is needed, the\n> parse_options() below intercepts \"-h\" by default.\n\nYep, sure was cargo-culted from somewhere else (my parse-options usage\nalways is), but I'm pretty sure it was from another place also using\nparse-options.  Probably one of these 15 places:\n\n $ comm -12 <(git grep -l parse-options builtin/ | sort) <(git grep -l\nstrcmp.*-h\\\\b builtin/ | sort)\nbuiltin/am.c\nbuiltin/branch.c\nbuiltin/checkout-index.c\nbuiltin/checkout--worker.c\nbuiltin/commit.c\nbuiltin/commit-tree.c\nbuiltin/gc.c\nbuiltin/ls-files.c\nbuiltin/merge.c\nbuiltin/merge-tree.c\nbuiltin/rebase.c\nbuiltin/rev-parse.c\nbuiltin/sparse-checkout.c\nbuiltin/submodule--helper.c\nbuiltin/update-index.c\n\n> > +     /* Parse arguments */\n> > +     argc = parse_options(argc, argv, prefix, mt_options,\n> > +                          merge_tree_usage, 0);\n> > +     if (o.real && o.trivial)\n> > +             die(_(\"--write-tree and --trivial-merge are incompatible\"));\n>\n> Shouldn't those two just be OPT_CMDMODE()? Then you get this\n> incompatibility checking for free. See 485fd2c3dae (cat-file: make\n> --batch-all-objects a CMDMODE, 2021-12-28).\n\nTIL.  Thanks.\n\n> > +     if (o.real || o.trivial) {\n> > +             expected_remaining_argc = (o.real ? 2 : 3);\n> > +             if (argc != expected_remaining_argc)\n> > +                     usage_with_options(merge_tree_usage, mt_options);\n> > +     } else {\n> > +             if (argc < 2 || argc > 3)\n> > +                     usage_with_options(merge_tree_usage, mt_options);\n> > +             o.real = (argc == 2);\n> > +     }\n>\n> And this can also be done like this, but I wonder if using\n> PARSE_OPT_STOP_AT_NON_OPTION and then routing to a sub-function wouldn't\n> be better, i.e. to treat these like sub-commands if they've got\n> different arity etc.\n\nNot sure what you mean; I already route to sub-functions.  But I\nshould definitely add PARSE_OPT_STOP_AT_NON_OPTION; it's unfortunate\nthat it's not the default.\n"},{"id":"446759","messageId":"CABPp-BE+4rZNP-5mT2MNOWR6y6BgEG6mt1r_qcrZtarom6aGsw@mail.gmail.com","threadId":"57288","inReplyTo":"220124.86h79tihjm.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 04/12] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-24T17:12:15Z","receivedAt":"2022-01-24T17:12:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jan 24, 2022 at 1:55 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > +     /*\n> > +      * TODO: Support subtree and other -X options?\n> > +     if (use_strategies_nr == 1 &&\n> > +         !strcmp(use_strategies[0]->name, \"subtree\"))\n> > +             opt.subtree_shift = \"\";\n> > +     for (x = 0; x < xopts_nr; x++)\n> > +             if (parse_merge_opt(&opt, xopts[x]))\n>\n> Better omitted WIP code in a non-RFC series?\n\nIt's RFC: https://lore.kernel.org/git/pull.1122.git.1642888562.gitgitgadget@gmail.com/\n\nBut yeah, I should drop it.  Previous rounds of this RFC submission\ngot me feedback that the other commented-out code bit I used to have\nwas something folks wanted in the initial version of the series so I\nuncommented and cleaned it up.  The fact that no one has commented on\nthis part suggests these options don't need to be supported from the\nstart.\n\n> > +                     die(_(\"Unknown strategy option: -X%s\"), xopts[x]);\n>\n> As a general issue with this series, die(), BUG() etc. messages should\n> start with a non-capital letter.\n\nRight, thanks for the reminder.  I'll go through and try to fix up.\n\n> > +     printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n>\n> And for both this...\n>\n> > +             printf(_(\"Conflicts!\\n\"));\n>\n> ... and this we can just use puts(). For the former it's just less code,\n> but for the latter translators also don't need to see the always-there\n> \\n in the translated message.\n\nMakes sense.\n\n> > +# This test is ort-specific\n> > +test \"${GIT_TEST_MERGE_ALGORITHM:-ort}\" = ort || {\n>\n> Is this ${} trickery really needed? We're not testing with \"set -u\". So just:\n>\n>         if test \"$GIT_...\" != \"ort\"\n>         then\n>                 ...\n>         fi\n\nAh, that would be simpler; thanks.\n\n> > +test_expect_success 'Barf on too many arguments' '\n> > +     test_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n> > +\n> > +     grep \"^usage: git merge-tree\" expect\n> > +'\n>\n> Nit: In most other tests these usage assertions are at the top of the\n> test, and for those we also make do with just testing the 129 exit code,\n> which is probably enough here too...\n\nI see a fair number of counterexamples searching for 129 in the test\nsuite, and I've been bitten enough times seeing tests expect an error\nbut get a different kind of error than the commit message stated they\nwere expecting that I prefer the extra check beyond the error code.\nAnyway, I'll leave this piece as-is.\n"},{"id":"446760","messageId":"CABPp-BF8oXCNpbXi1xb3Veh_Vi0uDXVX9VNOWMVz_zwjG=earQ@mail.gmail.com","threadId":"57288","inReplyTo":"220124.868rv5ih93.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-24T17:18:32Z","receivedAt":"2022-01-24T17:18:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jan 24, 2022 at 2:02 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Callers of `git merge-tree --write-tree` will often want to know which\n> > files had conflicts.  While they could potentially attempt to parse the\n> > CONFLICT notices printed, those messages are not meant to be machine\n> > readable.  Provide a simpler mechanism of just printing the files (in\n> > the same format as `git ls-files` with quoting, but restricted to\n> > [...]\n> > diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> > index 560640ad911..d8eeeb3f306 100644\n> > --- a/builtin/merge-tree.c\n> > +++ b/builtin/merge-tree.c\n> > @@ -11,6 +11,9 @@\n> >  #include \"blob.h\"\n> >  #include \"exec-cmd.h\"\n> >  #include \"merge-blobs.h\"\n> > +#include \"quote.h\"\n> > +\n> > +static int line_termination = '\\n';\n>\n> But unlike ls-files we don't do anything with line_termination as a !=\n> '\\n', maybe in a later commit?\n>\n> >  struct merge_list {\n> >       struct merge_list *next;\n> > @@ -395,7 +398,8 @@ struct merge_tree_options {\n> >  };\n> >\n> >  static int real_merge(struct merge_tree_options *o,\n> > -                   const char *branch1, const char *branch2)\n> > +                   const char *branch1, const char *branch2,\n> > +                   const char *prefix)\n> >  {\n> >       struct commit *parent1, *parent2;\n> >       struct commit_list *common;\n> > @@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,\n> >               o->show_messages = !result.clean;\n> >\n> >       printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> > +     if (!result.clean) {\n> > +             struct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n> > +             const char *last = NULL;\n> > +             int i;\n> > +\n> > +             merge_get_conflicted_files(&result, &conflicted_files);\n> > +             for (i = 0; i < conflicted_files.nr; i++) {\n> > +                     const char *name = conflicted_files.items[i].string;\n> > +                     if (last && !strcmp(last, name))\n> > +                             continue;\n> > +                     write_name_quoted_relative(\n> > +                             name, prefix, stdout, line_termination);\n>\n> But here it's never \\0 or whatever.\n\nCorrect, I didn't add any option for changing it.  But why hardcode it\nto \"\\n\"?  Leaving it this way makes it easier to change later if folks\nsay they want NUL-terminated output.  Since the series is RFC and the\noutput already has changed drastically and appears to be the primary\ndiscussion and disagreement point, I wanted to provide what seemed\nlike a reasonable suggestion and maintain flexiibility to address\nfeedback (though who knows -- I might need to just completely redo the\noutput again in ways much bigger than adding a -z option).\n"},{"id":"446761","messageId":"CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com","threadId":"57288","inReplyTo":"220124.864k5tigto.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 10/12] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-24T17:30:15Z","receivedAt":"2022-01-24T17:30:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jan 24, 2022 at 2:11 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Much like `git merge` updates the index with information of the form\n> >     (mode, oid, stage, name)\n> > provide this output for conflicted files for merge-tree as well.\n> > Provide an --exclude-oids-and-modes option for users to exclude the\n> > mode, oid, and stage and only get the list of conflicted filenames.\n> > [...]\n> > +--exclude-oids-and-modes::\n> > +     Instead of writing a list of (mode, oid, stage, path) tuples\n> > +     to output for conflicted files, just provide a list of\n> > +     filenames with conflicts.\n> > +\n> > [...]\n> > -This is a sequence of lines containing a filename on each line, quoted\n> > -as explained for the configuration variable `core.quotePath` (see\n> > -linkgit:git-config[1]).\n> > +This is a sequence of lines with the format\n> > +\n> > +     <mode> <object> <stage> <filename>\n> > +\n> > +The filename will be quoted as explained for the configuration\n> > +variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n> > +the `--exclude-oids-and-modes` option is passed, the mode, object, and\n> > +stage will be omitted.\n> >\n> >  Informational messages\n> >  ~~~~~~~~~~~~~~~~~~~~~~\n> >\n> >  This always starts with a blank line to separate it from the previous\n> > -section, and then has free-form messages about the merge, such as:\n> > +sections, and then has free-form messages about the merge, such as:\n> >\n> >    * \"Auto-merging <file>\"\n> >    * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n> > @@ -113,6 +123,14 @@ plumbing commands since the possibility of merge conflicts give it a\n> >  much higher chance of the command not succeeding (and NEWTREE containing\n> >  a bunch of stuff other than just a toplevel tree).\n> >\n> > +git-merge-tree was written to provide users with the same information\n> > +that they'd have access to if using `git merge`:\n> > +  * what would be written to the working tree (the <OID of toplevel tree>)\n> > +  * the higher order stages that would be written to the index (the\n> > +    <Conflicted file info>)\n> > +  * any messages that would have been printed to stdout (the <Informational\n> > +    messages>)\n> > +\n> >  GIT\n> >  ---\n> >  Part of the linkgit:git[1] suite\n> > diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> > index d8eeeb3f306..7aa7f9fd54a 100644\n> > --- a/builtin/merge-tree.c\n> > +++ b/builtin/merge-tree.c\n> > @@ -395,6 +395,7 @@ struct merge_tree_options {\n> >       int real;\n> >       int trivial;\n> >       int show_messages;\n> > +     int exclude_oids_and_modes;\n> >  };\n> >\n> >  static int real_merge(struct merge_tree_options *o,\n> > @@ -461,7 +462,11 @@ static int real_merge(struct merge_tree_options *o,\n> >               merge_get_conflicted_files(&result, &conflicted_files);\n> >               for (i = 0; i < conflicted_files.nr; i++) {\n> >                       const char *name = conflicted_files.items[i].string;\n> > -                     if (last && !strcmp(last, name))\n> > +                     struct stage_info *c = conflicted_files.items[i].util;\n> > +                     if (!o->exclude_oids_and_modes)\n> > +                             printf(\"%06o %s %d\\t\",\n> > +                                    c->mode, oid_to_hex(&c->oid), c->stage);\n> > +                     else if (last && !strcmp(last, name))\n> >                               continue;\n> >                       write_name_quoted_relative(\n> >                               name, prefix, stdout, line_termination);\n> > @@ -495,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >                        N_(\"do a trivial merge only\")),\n> >               OPT_BOOL(0, \"messages\", &o.show_messages,\n> >                        N_(\"also show informational/conflict messages\")),\n> > +             OPT_BOOL_F(0, \"exclude-oids-and-modes\",\n> > +                        &o.exclude_oids_and_modes,\n> > +                        N_(\"list conflicted files without oids and modes\"),\n> > +                        PARSE_OPT_NONEG),\n> >               OPT_END()\n> >       };\n>\n> Perhaps this really is the last formatting information anyone will want,\n> but with a default of \"<mode> <object> <stage> <filename>\" being made\n> \"<stage> <filename>\" with --exclude-oids-and-modes perhaps we'll want\n> --exclude-all-except-filename etc. later.\n\nUm, that's actually what this option does.  Maybe my chosen name was bad.\n\n--name-only like ls-files uses would have been nice, but it's\nmisleading since it only affects the <conflict info> section of the\noutput, not the printed tree or the informational messages.\n\n> It seems a lot simpler for new code to just support a --conflict-format\n> option, lifting some code from the in-flight\n> https://lore.kernel.org/git/db058bf670c5668fc5b95baf83667cc282cb739b.1641978175.git.dyroneteng@gmail.com/\n>\n> I.e. that inner loop becomes a slightly more verbose strbuf_expand(),\n> but it's all easy boilerplate code.\n>\n> Then we just feed \"%(objectmode) %(objectname) %(objectstage)\n> %(filename)\" into it by default, and allow the user to customize it.\n\n\"simpler\"?  More flexible certainly.\n\nI'm not sure that the flexibility is warranted, in this case, though.\nIn ls-trees, where users don't need to process the output and can feed\nit directly to something else, that flexibility makes sense.  But here\nthe output *needs* special post-processing anyway since it's mixing\nthree different types of output by default: top-level tree, conflicted\nfile info, and informational conflict messages.  (In my previous round\nI tried to split these kinds of output to separate locations so they\ncould be parsed separately, but both Dscho and Christian didn't like\nthat).\n\nSo, the default I figured should be to just provide all the\ninformation and just let others process it as wanted.  But Taylor had\nsaid earlier that if there were conflicts the only information he\nwanted was a list of conflicted files (no stages, oids, or modes), so\nI figured an option making that a bit easier to extract was\nworthwhile.  And that's what this patch is about.\n"},{"id":"446826","messageId":"CABPp-BFcFt2GqsGi57dn6vWNqdnF+_Rt0BgpAWgD=taXWAS9AA@mail.gmail.com","threadId":"57288","inReplyTo":"220124.86czkhihcu.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-25T01:59:16Z","receivedAt":"2022-01-25T04:04:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jan 24, 2022 at 2:00 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> > [...]\n> > +     /* Hack to pre-allocate olist to the desired size */\n> > +     ALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n> > +                olist.alloc);\n>\n> Perhaps just add a string_list_grow()? But I wonder if this is really\n> needed v.s. just using the default growing pattern here.\n\nA string_list_grow() would probably be helpful to add at some point;\nthen it could also be used in process_entries().\n\n> > +\n> > +     /* Put every entry from output into olist, then sort */\n> > +     strmap_for_each_entry(&opti->output, &iter, e) {\n> > +             string_list_append(&olist, e->key)->util = e->value;\n> > +     }\n> > +     string_list_sort(&olist);\n> > +\n> > +     /* Iterate over the items, printing them */\n> > +     for (i = 0; i < olist.nr; ++i) {\n> > +             struct strbuf *sb = olist.items[i].util;\n> > +\n> > +             printf(\"%s\", sb->buf);\n> > +     }\n>\n> Shorter/nicer:\n>\n>         for_each_string_list_item(item, &olist) {\n>                 struct strbuf *sb = item->util;\n>                 puts(sb->buf);\n>         }\n\nHow did I not know about and not find for_each_string_list_item() when\nI was writing this code a couple years ago?  (and still didn't learn\nof it until now?)\n\nThanks for the pointer.  Won't change anything right now, though, since...\n\n> > -     if (display_update_msgs) {\n> > -             struct merge_options_internal *opti = result->priv;\n> > -             struct hashmap_iter iter;\n> > -             struct strmap_entry *e;\n> > -             struct string_list olist = STRING_LIST_INIT_NODUP;\n> > -             int i;\n> > -\n> > -             if (opt->record_conflict_msgs_as_headers)\n> > -                     BUG(\"Either display conflict messages or record them as headers, not both\");\n> > -\n> > -             trace2_region_enter(\"merge\", \"display messages\", opt->repo);\n> > -\n> > -             /* Hack to pre-allocate olist to the desired size */\n> > -             ALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n> > -                        olist.alloc);\n> > -\n> > -             /* Put every entry from output into olist, then sort */\n> > -             strmap_for_each_entry(&opti->output, &iter, e) {\n> > -                     string_list_append(&olist, e->key)->util = e->value;\n> > -             }\n> > -             string_list_sort(&olist);\n> > -\n> > -             /* Iterate over the items, printing them */\n> > -             for (i = 0; i < olist.nr; ++i) {\n> > -                     struct strbuf *sb = olist.items[i].util;\n> > -\n> > -                     printf(\"%s\", sb->buf);\n> > -             }\n> > -             string_list_clear(&olist, 0);\n>\n> Ah, at this point I see you're just moving code around :) Sending this\n> anyway in case it's useful :)\n\nyep, so I won't change anything now, but yes th\nfor_each_string_list_item() tip is still useful as a heads up.\nThanks.\n"},{"id":"446854","messageId":"nycvar.QRO.7.76.6.2201251804250.2121@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"05bd17686e1404c81542b6bbf69dcd3decb83c5b.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/12] merge-tree: implement real merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-25T17:07:32Z","receivedAt":"2022-01-25T17:09:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> +The second form is deprecated; it is kept for backward compatibility\n> +reasons but may be deleted in the future.  It will only do a trivial\n> +merge.  It reads three tree-ish, and outputs trivial merge results and\n> +conflicting stages to the standard output in a semi-diff format.\n> +Since this was designed for higher level scripts to consume and merge\n> +the results back into the index, it omits entries that match\n> +<branch1>.  The result of this second form is is similar to what\n\nThere is a double \"is\" in this line. Taking a step back, I would suggest\nto not only remove this paragraph, but to mark the `[--trivial-merge]`\noption clearly as `(DEPRECATED)`.\n\n> +three-way 'git read-tree -m' does, but instead of storing the results\n> +in the index, the command outputs the entries to the standard output.\n> +This form not only has limited applicability, the output format is\n> +also difficult to work with, and it will generally be less performant\n> +than the first form even on successful merges (especially if working\n> +in large repositories).  The remainder of this manual will only\n> +discuss the first form.\n\nThank you,\nDscho\n"},{"id":"446926","messageId":"CAP8UFD1-=RDx5=JpHEp=sFEOWr2MP-YovOPE7aTydrPLoVGa5w@mail.gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-26T08:48:04Z","receivedAt":"2022-01-26T08:48:17Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for\n> suggestions and comments on v2):\n>\n>  * Significant changes to output format:\n>    * Flags no longer take a filename for additional output; they write to\n>      stdout instead.\n>    * More information included by default when there are conflicts (no need\n>      to request it with additional flags, instead flags can be used to\n>      suppress it).\n>    * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n>      information -- when there are conflicts. Add a flag to only list\n>      conflicted files if that's preferred.\n\nThe above changes seem good to me.\n\n>  * Much more thorough manual for git-merge-tree.txt\n>  * Renamed option from --real to --write-tree\n>  * Accept an optional --trivial-merge option to get old style merge-tree\n>    behavior\n>  * Allow both --write-tree and --trivial-merge to be omitted since we can\n>    deduce which from number of arguments\n\nI still think that it might be simpler and cleaner to leave 'git\nmerge-tree' alone for now, and just add a new command named for\nexample 'git write-merge-tree'. Later we can always add flags to 'git\nmerge-tree' or add 'git trivial-merge-tree' as an alias for 'git\nmerge-tree', and eventually slowly switch 'git merge-tree' to mean\nonly 'git write-merge-tree' if that's where we want to go.\n\n>  * Document exit code when the merge cannot be run (so we can distinguish\n>    other error cases from conflicts)\n>  * testcase cleanups: test_tick, early skip of test when using recursive\n>    backend, variable renames, etc.\n>  * various minor code cleanups\n>  * Add a new --allow-unrelated-histories option (with same meaning as the\n>    one used in git merge)\n\nThe above changes seem good to me too.\n\n> Stuff intentionally NOT included, but which others seemed to feel strongly\n> about; they'd need to convince me more on these:\n>\n>  * Any form of diff output[1]\n\nIt's not a big issue for me to not include them right now as long as\nit's possible to add cli options later that add them. The reason is\nthat I think in many cases when there are conflicts, the conflicts\nwill be small and the user will want to see them. So it would be\nsimpler to just have an option to show any conflict right away, rather\nthan have the user launch another command (a diff-tree against which\ntree and with which options?).\n\nThanks for working on this anyway!\n"},{"id":"446928","messageId":"CAP8UFD3_Dx2aZObbUp7dHkFCp5w9mJG-s03Soz=9YDU=yE2NoQ@mail.gmail.com","threadId":"57288","inReplyTo":"05bd17686e1404c81542b6bbf69dcd3decb83c5b.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 04/12] merge-tree: implement real merges","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-26T09:44:58Z","receivedAt":"2022-01-26T09:45:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> From: Elijah Newren <newren@gmail.com>\n>\n> This adds the ability to perform real merges rather than just trivial\n> merges (meaning handling three way content merges, recursive ancestor\n> consolidation, renames, proper directory/file conflict handling, and so\n> forth).  However, unlike `git merge`, the working tree and index are\n> left alone and no branch is updated.\n>\n> The only output is:\n>   - the toplevel resulting tree printed on stdout\n>   - exit status of 0 (clean) or 1 (conflicts present)\n\nThe exit status can now actually be something other than 0 and 1\naccording to the doc and code below.\n\n> +Performs a merge, but does not make any new commits and does not read\n> +from or write to either the working tree or index.\n> +\n> +The first form will merge the two branches, doing a full recursive\n> +merge with rename detection.\n\nMaybe this could already tell that the first form will also write a\ntree with the result of the merge (even in case of conflict) as this\ncould help understand the reason why the associated option is called\n'--write-tree'. It could also help to say that we call such a merge a\n'real' merge.\n\n> The rest of this manual (other than the\n> +next paragraph) describes the first form in more detail -- including\n> +options, output format, exit status, and usage notes.\n\n>  static int real_merge(struct merge_tree_options *o,\n>                       const char *branch1, const char *branch2)\n>  {\n> -       die(_(\"real merges are not yet implemented\"));\n> +       struct commit *parent1, *parent2;\n> +       struct commit_list *common;\n> +       struct commit_list *merge_bases = NULL;\n> +       struct commit_list *j;\n> +       struct merge_options opt;\n> +       struct merge_result result = { 0 };\n> +\n> +       parent1 = get_merge_parent(branch1);\n> +       if (!parent1)\n> +               help_unknown_ref(branch1, \"merge\",\n> +                                _(\"not something we can merge\"));\n\nThe second argument is supposed to be the command (it's called \"cmd\"),\nso maybe \"merge-tree\" instead of \"merge\".\n\n> +       parent2 = get_merge_parent(branch2);\n> +       if (!parent2)\n> +               help_unknown_ref(branch2, \"merge\",\n> +                                _(\"not something we can merge\"));\n\nidem\n\n> +       opt.show_rename_progress = 0;\n> +\n> +       opt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */\n> +       opt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */\n\nI think just:\n\n       opt.branch1 = branch1\n       opt.branch2 = branch2\n\nmight be better for users as it should show the name as it was passed\nto the command.\n\n> +       merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> +       printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n\nI wonder if we can actually always output a valid tree when\nresult.clean < 0. In case we might not, the printing should go a few\nlines below.\n\n> +       if (result.clean < 0)\n> +               die(_(\"failure to merge\"));\n> +       else if (!result.clean)\n\nThe \"else\" is not necessary above.\n\n> +               printf(_(\"Conflicts!\\n\"));\n> +       merge_finalize(&opt, &result);\n> +       return !result.clean; /* result.clean < 0 handled above */\n>  }\n\n> diff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\n> new file mode 100755\n> index 00000000000..e03688515c5\n> --- /dev/null\n> +++ b/t/t4301-merge-tree-real.sh\n\nI wonder if it would be better named 't4301-merge-tree-write-tree.sh'...\n\n> @@ -0,0 +1,87 @@\n> +#!/bin/sh\n> +\n> +test_description='git merge-tree --write-tree'\n\n... especially given this description.\n"},{"id":"446929","messageId":"CAP8UFD0fRTw0Uh6oWNtCdotRb3F6fvZnpUwm=vT0qzmfeuBvEQ@mail.gmail.com","threadId":"57288","inReplyTo":"2f296aeeefbf8340cfb8b7fa4fef5ad49c8b4aa1.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/12] merge-tree: support including merge messages in output","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-26T10:42:20Z","receivedAt":"2022-01-26T10:42:42Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n>  EXIT STATUS\n>  -----------\n> @@ -72,7 +102,8 @@ be used as a part of a series of steps such as\n>\n>  However, it does not quite fit into the same category of low-level\n>  plumbing commands since the possibility of merge conflicts give it a\n> -much higher chance of the command not succeeding.\n> +much higher chance of the command not succeeding (and NEWTREE containing\n> +a bunch of stuff other than just a toplevel tree).\n\nIs this hunk really related to this commit or should it go into a\nprevious commit?\n\n> @@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,\n>                 commit_list_insert(j->item, &merge_bases);\n>\n>         merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> -       printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> +\n>         if (result.clean < 0)\n>                 die(_(\"failure to merge\"));\n\nSo this addresses the comment I made in a previous commit related to\nthe fact that if result.clean < 0 we might not have a valid tree that\nwe can print. I think though that it would be better if that was\naddressed in a previous commit.\n\n> -       else if (!result.clean)\n> -               printf(_(\"Conflicts!\\n\"));\n\nOk, so we don't print \"Conflicts!\\n\" now, which makes me wonder if we\nshould have printed it in the first place in previous commits.\n\n>         if (o.real && o.trivial)\n>                 die(_(\"--write-tree and --trivial-merge are incompatible\"));\n> +       if (!o.real && original_argc < argc)\n> +               die(_(\"--write-tree must be specified if any other options are\"));\n\nIs this necessary? It looks to me like another thing that would be\nsimplified if we were just adding a new command...\n"},{"id":"446931","messageId":"CAP8UFD0+ui2uJtyNSQ3Cq4k+9SPpjT08=CBhMFNJ0ni2tLQQPw@mail.gmail.com","threadId":"57288","inReplyTo":"35e0ed9271a0229fe2acd2385a7e4171d4dfe077.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-26T10:55:30Z","receivedAt":"2022-01-26T10:55:44Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> After a merge, this function allows the user to extract the same\n> information that would be printed by `ls-files -u` -- conflicted\n\nThis made me wonder if \"-- conflicted\" should be part of the `ls-files\n-u` command. Maybe \"by `ls-files -u`, which means\" would make things a\nbit clearer.\n\n> files with their mode, oid, and stage.\n"},{"id":"446934","messageId":"CAP8UFD0iQg4nL6eSTDbEu8t6h+K0H+nGF8y_N0z3XyjH+KGORA@mail.gmail.com","threadId":"57288","inReplyTo":"35e0ed9271a0229fe2acd2385a7e4171d4dfe077.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-26T11:07:23Z","receivedAt":"2022-01-26T11:07:37Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n\n> +void merge_get_conflicted_files(struct merge_result *result,\n> +                               struct string_list *conflicted_files)\n> +{\n> +       struct hashmap_iter iter;\n> +       struct strmap_entry *e;\n> +       struct merge_options_internal *opti = result->priv;\n> +\n> +       strmap_for_each_entry(&opti->conflicted, &iter, e) {\n> +               const char *path = e->key;\n> +               struct conflict_info *ci = e->value;\n> +               int i;\n> +\n> +               VERIFY_CI(ci);\n> +\n> +               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n> +                       struct stage_info *si;\n> +\n> +                       if (!(ci->filemask & (1ul << i)))\n> +                               continue;\n> +\n> +                       si = xmalloc(sizeof(*si));\n\nIt's probably a premature optimization, so feel free to ignore, but as\nMERGE_BASE and MERGE_SIDE2 are constants, and ci->filemask is constant\ninside the 'for' loop, we could compute before the 'for' loop how many\n'struct stage_info' we will need and allocate them all at once before\nthe 'for' loop.\n\n> +                       si->stage = i+1;\n> +                       si->mode = ci->stages[i].mode;\n> +                       oidcpy(&si->oid, &ci->stages[i].oid);\n> +                       string_list_append(conflicted_files, path)->util = si;\n> +               }\n> +       }\n> +       /* string_list_sort() uses a stable sort, so we're good */\n> +       string_list_sort(conflicted_files);\n> +}\n"},{"id":"446937","messageId":"nycvar.QRO.7.76.6.2201261250380.6842@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CAP8UFD1-=RDx5=JpHEp=sFEOWr2MP-YovOPE7aTydrPLoVGa5w@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-26T12:02:54Z","receivedAt":"2022-01-26T12:03:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Wed, 26 Jan 2022, Christian Couder wrote:\n\n> On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> >  * Accept an optional --trivial-merge option to get old style merge-tree\n> >    behavior\n> >  * Allow both --write-tree and --trivial-merge to be omitted since we can\n> >    deduce which from number of arguments\n>\n> I still think that it might be simpler and cleaner to leave 'git\n> merge-tree' alone for now, and just add a new command named for\n> example 'git write-merge-tree'.\n\nThat would assume that the original `git merge-tree` implementation was\nuseful. That notion has been thoroughly refuted in the meantime, though.\n\nI am really opposed to introducing a new command here. Elijah took the\nbest approach we can take here: save the `merge-tree` command by teaching\nit to do something useful.\n\n> Later we can always add flags to 'git merge-tree' or add 'git\n> trivial-merge-tree' as an alias for 'git merge-tree', and eventually\n> slowly switch 'git merge-tree' to mean only 'git write-merge-tree' if\n> that's where we want to go.\n\nI suggested before, and seem to need to repeat again, that we need to let\nourselves be guided less by hypothetical scenarios, and more by actual,\nconcrete use cases where the revamped `merge-tree` command is useful.\n\nAnd since I already provided some feedback based on my work from working\non a server-side backend, I am fairly certain that we already have a\npretty good idea where we want to go.\n\n> > Stuff intentionally NOT included, but which others seemed to feel strongly\n> > about; they'd need to convince me more on these:\n> >\n> >  * Any form of diff output[1]\n>\n> It's not a big issue for me to not include them right now as long as\n> it's possible to add cli options later that add them.\n\nBut why? That _so_ smells like a hypothetical scenario.\n\nWe do not need the diffs. It is highly unlikely that the server-side wants\nto have diffs, and if a user does want the diffs, it is very, very easy to\ngenerate them by chaining low-level commands.\n\nSo there is absolutely no need for `git merge-tree` to produce diffs.\n\n> The reason is that I think in many cases when there are conflicts, the\n> conflicts will be small and the user will want to see them. So it would\n> be simpler to just have an option to show any conflict right away,\n> rather than have the user launch another command (a diff-tree against\n> which tree and with which options?).\n\nThat assumes that server-side merge UIs will present merge conflicts in\nthe form of diffs containing merge conflict markers. Which I don't think\nwill happen, like, ever.\n\nIn short: I completely disagree that we should introduce a new command,\nand I also completely disagree that the `merge-tree` command should output\nany diffs.\n\nI do agree that we need to be mindful of what we actually need, and in\nthat regard, I reiterate that we need to let concrete use cases guide us.\nAs part of GitLab, you might be in an excellent position to look at\nGitLab's concrete server-side needs when it comes to use `git merge-tree`\nto perform merges.\n\nCiao,\nDscho\n"},{"id":"446964","messageId":"CAP8UFD2qFmZ2Adk71SQw9xtq5keZ=d2hcMwF=fs9OtW4==0ZYg@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2201261250380.6842@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-26T14:44:03Z","receivedAt":"2022-01-26T14:44:22Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Wed, Jan 26, 2022 at 1:03 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian,\n>\n> On Wed, 26 Jan 2022, Christian Couder wrote:\n>\n> > On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> >\n> > >  * Accept an optional --trivial-merge option to get old style merge-tree\n> > >    behavior\n> > >  * Allow both --write-tree and --trivial-merge to be omitted since we can\n> > >    deduce which from number of arguments\n> >\n> > I still think that it might be simpler and cleaner to leave 'git\n> > merge-tree' alone for now, and just add a new command named for\n> > example 'git write-merge-tree'.\n>\n> That would assume that the original `git merge-tree` implementation was\n> useful. That notion has been thoroughly refuted in the meantime, though.\n>\n> I am really opposed to introducing a new command here. Elijah took the\n> best approach we can take here: save the `merge-tree` command by teaching\n> it to do something useful.\n\nI think it's a question of point of view. If a command is completely\nuseless, then most of the time it needs to die, not be \"saved\". We\nwould need good statistics, but I doubt we have \"saved\" many useless\ncommands before, compared to commands we have just killed.\n\n> > Later we can always add flags to 'git merge-tree' or add 'git\n> > trivial-merge-tree' as an alias for 'git merge-tree', and eventually\n> > slowly switch 'git merge-tree' to mean only 'git write-merge-tree' if\n> > that's where we want to go.\n>\n> I suggested before, and seem to need to repeat again, that we need to let\n> ourselves be guided less by hypothetical scenarios, and more by actual,\n> concrete use cases where the revamped `merge-tree` command is useful.\n\nOk, see below.\n\n> And since I already provided some feedback based on my work from working\n> on a server-side backend, I am fairly certain that we already have a\n> pretty good idea where we want to go.\n>\n> > > Stuff intentionally NOT included, but which others seemed to feel strongly\n> > > about; they'd need to convince me more on these:\n> > >\n> > >  * Any form of diff output[1]\n> >\n> > It's not a big issue for me to not include them right now as long as\n> > it's possible to add cli options later that add them.\n>\n> But why? That _so_ smells like a hypothetical scenario.\n>\n> We do not need the diffs. It is highly unlikely that the server-side wants\n> to have diffs, and if a user does want the diffs, it is very, very easy to\n> generate them by chaining low-level commands.\n>\n> So there is absolutely no need for `git merge-tree` to produce diffs.\n>\n> > The reason is that I think in many cases when there are conflicts, the\n> > conflicts will be small and the user will want to see them. So it would\n> > be simpler to just have an option to show any conflict right away,\n> > rather than have the user launch another command (a diff-tree against\n> > which tree and with which options?).\n>\n> That assumes that server-side merge UIs will present merge conflicts in\n> the form of diffs containing merge conflict markers. Which I don't think\n> will happen, like, ever.\n\nPlease take a look at:\n\nhttps://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor\n\nAs you can see in the image there are conflict markers in the file\ndisplayed by the server UI.\n\n> In short: I completely disagree that we should introduce a new command,\n> and I also completely disagree that the `merge-tree` command should output\n> any diffs.\n>\n> I do agree that we need to be mindful of what we actually need, and in\n> that regard, I reiterate that we need to let concrete use cases guide us.\n> As part of GitLab, you might be in an excellent position to look at\n> GitLab's concrete server-side needs when it comes to use `git merge-tree`\n> to perform merges.\n\nI hope I provided a concrete use case with the link above.\n"},{"id":"447213","messageId":"nycvar.QRO.7.76.6.2201281343360.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CAP8UFD2qFmZ2Adk71SQw9xtq5keZ=d2hcMwF=fs9OtW4==0ZYg@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T12:58:55Z","receivedAt":"2022-01-28T12:59:05Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Wed, 26 Jan 2022, Christian Couder wrote:\n\n> On Wed, Jan 26, 2022 at 1:03 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Wed, 26 Jan 2022, Christian Couder wrote:\n> >\n> > > The reason is that I think in many cases when there are conflicts,\n> > > the conflicts will be small and the user will want to see them. So\n> > > it would be simpler to just have an option to show any conflict\n> > > right away, rather than have the user launch another command (a\n> > > diff-tree against which tree and with which options?).\n> >\n> > That assumes that server-side merge UIs will present merge conflicts in\n> > the form of diffs containing merge conflict markers. Which I don't think\n> > will happen, like, ever.\n>\n> Please take a look at:\n>\n> https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor\n>\n> As you can see in the image there are conflict markers in the file\n> displayed by the server UI.\n\nPlease note the difference between what I wrote above (present merge\nconflicts in the form of diffs containing merge conflict markers) and what\nis shown in the document you linked to (present a file annotated with\nmerge conflict markers).\n\nThere is no diff in that page.\n\nWhat's more: there are not only conflict markers in the editor, there is\nclearly a visual marker next to the line number that indicates that the\neditor has a fundamental understanding where the conflict markers are.\nWhich means that the conflict markers must have been generated\nindependently of Git rather than parsed in some random diff that was\nproduced by Git.\n\nIn other words: you are making my case for me that `git merge-tree` should\nnot generate diff output because it would not even be used.\n\n> > In short: I completely disagree that we should introduce a new command,\n> > and I also completely disagree that the `merge-tree` command should output\n> > any diffs.\n> >\n> > I do agree that we need to be mindful of what we actually need, and in\n> > that regard, I reiterate that we need to let concrete use cases guide us.\n> > As part of GitLab, you might be in an excellent position to look at\n> > GitLab's concrete server-side needs when it comes to use `git merge-tree`\n> > to perform merges.\n>\n> I hope I provided a concrete use case with the link above.\n\nSorry, I apparently was a bit unclear.\n\nIn the context of discussing `git merge-tree`, a low-level Git command,\nwhen I talk about a user, I do not mean a human being, but a program that\ncalls that command and parses its output.\n\nCorollary: by \"use case\" I refer to a concrete implementation of a\nserver-side merge operation, I refer to backend code that currently calls\ninto libgit2 to perform the merge, and would benefit from calling `git\nmerge-tree` instead. Such a use case informs us about the type and amount\nof information that is required of the code that is currently being\ndiscussed in this mail thread. And I highly doubt that you will find such\na use case that wants libgit2 (and later, `git merge-tree`) to generate\ndiffs. Because _diffs_ are certainly _not_ what is consumed by the inline\neditor you referenced.\n\nOf course, I am still left guessing what the server-side needs concretely,\nbecause that is not at all obvious from the user-facing web site to which\nyou sent me. What is needed is a good, hard look at the actual _code_, the\ncode that calls into libgit2 to perform a merge, and that could instead\nspawn a `git merge-tree` process to accomplish the same thing.\n\nWe need to get away from hypothetical scenarios. They're not helping.\n\nCiao,\nJohannes\n"},{"id":"447214","messageId":"CAP8UFD1iJs0iGe430_Y=S6_nadS8AfBr_w0MuX-0H4ObcwDNdg@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2201281343360.347@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-28T13:37:44Z","receivedAt":"2022-01-28T13:37:59Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Hi Dscho,\n\nOn Fri, Jan 28, 2022 at 1:58 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Christian,\n>\n> On Wed, 26 Jan 2022, Christian Couder wrote:\n>\n> > On Wed, Jan 26, 2022 at 1:03 PM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> > >\n> > > On Wed, 26 Jan 2022, Christian Couder wrote:\n> > >\n> > > > The reason is that I think in many cases when there are conflicts,\n> > > > the conflicts will be small and the user will want to see them. So\n> > > > it would be simpler to just have an option to show any conflict\n> > > > right away, rather than have the user launch another command (a\n> > > > diff-tree against which tree and with which options?).\n> > >\n> > > That assumes that server-side merge UIs will present merge conflicts in\n> > > the form of diffs containing merge conflict markers. Which I don't think\n> > > will happen, like, ever.\n> >\n> > Please take a look at:\n> >\n> > https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor\n> >\n> > As you can see in the image there are conflict markers in the file\n> > displayed by the server UI.\n>\n> Please note the difference between what I wrote above (present merge\n> conflicts in the form of diffs containing merge conflict markers) and what\n> is shown in the document you linked to (present a file annotated with\n> merge conflict markers).\n>\n> There is no diff in that page.\n\nThe server UI could just get a diff with the conflicts inside instead\nof the full files with conflict inside, as the diff would be smaller\nto parse than the full files. So even if it's not shown, the diff\ncould still be useful.\n\nAlso just above the section of the link I sent, there is this section\n\nhttps://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-interactive-mode\n\nwhere one can see diff markers in the image. There are no conflict\nmarkers in those images, but it's possible that a future UI could\ncombine both a diff and conflict markers.\n\nAlso please note that I don't absolutely require diffs. At the\nbeginning of the paragraph from my original email that you quoted\nabove, there was:\n\n\"It's not a big issue for me to not include them right now as long as\nit's possible to add cli options later that add them.\"\n\nSo I was just saying that the format and code should be flexible\nenough to be able to easily accommodate sending further data like\ndiffs with additional options. I think it's a very reasonable request.\nSo please don't make it a huge issue. You can always NACK a patch\nadding such an option later.\n\n> What's more: there are not only conflict markers in the editor,\n\nYou don't see the \">>>>>>>\"?\n\n> there is\n> clearly a visual marker next to the line number that indicates that the\n> editor has a fundamental understanding where the conflict markers are.\n\nYeah, so this shows that those markers can be important for the editor.\n\n> Which means that the conflict markers must have been generated\n> independently of Git rather than parsed in some random diff that was\n> produced by Git.\n\nWhy couldn't they be generated by Git and then just parsed from a diff\nin the future, even if that was not the case here?\n\n> In other words: you are making my case for me that `git merge-tree` should\n> not generate diff output because it would not even be used.\n\nThe other link above in this email actually shows that diffs are used\nright now to resolve conflicts.\n\n> > > In short: I completely disagree that we should introduce a new command,\n> > > and I also completely disagree that the `merge-tree` command should output\n> > > any diffs.\n> > >\n> > > I do agree that we need to be mindful of what we actually need, and in\n> > > that regard, I reiterate that we need to let concrete use cases guide us.\n> > > As part of GitLab, you might be in an excellent position to look at\n> > > GitLab's concrete server-side needs when it comes to use `git merge-tree`\n> > > to perform merges.\n> >\n> > I hope I provided a concrete use case with the link above.\n>\n> Sorry, I apparently was a bit unclear.\n>\n> In the context of discussing `git merge-tree`, a low-level Git command,\n> when I talk about a user, I do not mean a human being, but a program that\n> calls that command and parses its output.\n\nSo it could very well parse diffs containing conflict markers and show\nthose conflict markers.\n\n[...]\n\n> Of course, I am still left guessing what the server-side needs concretely,\n> because that is not at all obvious from the user-facing web site to which\n> you sent me. What is needed is a good, hard look at the actual _code_, the\n> code that calls into libgit2 to perform a merge, and that could instead\n> spawn a `git merge-tree` process to accomplish the same thing.\n>\n> We need to get away from hypothetical scenarios. They're not helping.\n\nI am not even asking for a feature, just to make it possible to extend\nthe output of a brand new command in an RFC with some possibly useful\nthings, and you are making such requests...\n\nPlease relax a bit on this.\n"},{"id":"447219","messageId":"nycvar.QRO.7.76.6.2201281647361.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CAP8UFD1iJs0iGe430_Y=S6_nadS8AfBr_w0MuX-0H4ObcwDNdg@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T16:05:30Z","receivedAt":"2022-01-28T16:05:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Fri, 28 Jan 2022, Christian Couder wrote:\n\n> On Fri, Jan 28, 2022 at 1:58 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Wed, 26 Jan 2022, Christian Couder wrote:\n> >\n> > > On Wed, Jan 26, 2022 at 1:03 PM Johannes Schindelin\n> > > <Johannes.Schindelin@gmx.de> wrote:\n> > > >\n> > > > On Wed, 26 Jan 2022, Christian Couder wrote:\n> > > >\n> > > > > The reason is that I think in many cases when there are conflicts,\n> > > > > the conflicts will be small and the user will want to see them. So\n> > > > > it would be simpler to just have an option to show any conflict\n> > > > > right away, rather than have the user launch another command (a\n> > > > > diff-tree against which tree and with which options?).\n> > > >\n> > > > That assumes that server-side merge UIs will present merge conflicts in\n> > > > the form of diffs containing merge conflict markers. Which I don't think\n> > > > will happen, like, ever.\n> > >\n> > > Please take a look at:\n> > >\n> > > https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor\n> > >\n> > > As you can see in the image there are conflict markers in the file\n> > > displayed by the server UI.\n> >\n> > Please note the difference between what I wrote above (present merge\n> > conflicts in the form of diffs containing merge conflict markers) and what\n> > is shown in the document you linked to (present a file annotated with\n> > merge conflict markers).\n> >\n> > There is no diff in that page.\n>\n> The server UI could just get a diff with the conflicts inside instead\n> of the full files with conflict inside, as the diff would be smaller\n> to parse than the full files. So even if it's not shown, the diff\n> could still be useful.\n\nYou really need to get away from talking about this in hypothetical terms.\n\n> Also just above the section of the link I sent, there is this section\n>\n> https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-interactive-mode\n>\n> where one can see diff markers in the image.\n\nThat's a side-by-side diff. Git cannot even produce those.\n\n> > What's more: there are not only conflict markers in the editor,\n>\n> You don't see the \">>>>>>>\"?\n\nYes, I do. And not only that. I also see that the editor knows very\nspecifically where the conflict happens.\n\nAnd since any file can contain `>>>>>>>` _without_ it being a conflict\nmarker, the editor most likely does not parse the output of Git that\ncontains a conflict marker. At least I hope it does not because it would\nthen very easily be confused by strings that look like conflict markers,\nbut aren't.\n\nThink about our very own test suite, and why we specifically set\n`conflict-marker-size=32` for those files. Same reason why the server\nbackend cannot simply ingest files with conflict markers and then hope to\nfigure out which `>>>>>>>` are real conflict markers and which are not.\n\n> > there is clearly a visual marker next to the line number that\n> > indicates that the editor has a fundamental understanding where the\n> > conflict markers are.\n>\n> Yeah, so this shows that those markers can be important for the editor.\n\nOf course they are important! That's my point!\n\n> > In other words: you are making my case for me that `git merge-tree` should\n> > not generate diff output because it would not even be used.\n>\n> The other link above in this email actually shows that diffs are used\n> right now to resolve conflicts.\n\nIt shows that Git was not used to generate the diff, is what it shows.\n\nI see that you are still trying to guess what the server-side needs\nactually are. It really is time to stop guessing. So I will keep\nchallenging you to actually look at the GitLab code, to take a stab at\nteaching it to use `git merge-tree` to perform merges. And then to come\nback with what you learned. I guarantee you that that will be multiple\ntimes more useful than talking about it in hypotheticals.\n\nAnd you are in such an almost unique position to contribute to this patch\nseries, to provide that very valuable feedback how `git merge-tree` could\nbe improved to support actual, real-life server-side code that is\ncurrently in use! So why not make the most out of it?\n\nCiao,\nJohannes\n"},{"id":"447220","messageId":"nycvar.QRO.7.76.6.2201281707250.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"095aa266c2bfdda47ed722fbc5a0d9c94132fbf1.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T16:09:25Z","receivedAt":"2022-01-28T16:09:34Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> No functional changes included in this patch; it's just a preparatory\n> step to allow the printed messages to be handled differently by other\n> callers, such as in `git merge-tree --write-tree`.\n\nLooks good. FWIW I cheated and looked at the output of\n\n\tgit show pr-1122/newren/in-core-merge-tree-v1~7 \\\n\t\t--color-moved \\\n\t\t--color-moved-ws=allow-indentation-change\n\n(after fetching the tag from https://github.com/gitgitgadget/git) instead\nof this patch (which is a lot easier to digest for me, because of the\ncolor-coding, and since GitGitGadget sent the patch, I know that it is\nidentical to the commit).\n\nCiao,\nDscho\n"},{"id":"447221","messageId":"nycvar.QRO.7.76.6.2201281716420.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"e3ef17eb46fdfd759030761ab6d7c35fbf24ee0f.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 06/12] merge-ort: allow update messages to be written to different file stream","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T16:31:03Z","receivedAt":"2022-01-28T16:31:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> diff --git a/merge-ort.c b/merge-ort.c\n> index f9e35b0f96b..b78dde55ad9 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>  }\n>\n>  void merge_display_update_messages(struct merge_options *opt,\n> -\t\t\t\t   struct merge_result *result)\n> +\t\t\t\t   struct merge_result *result,\n> +\t\t\t\t   FILE *stream)\n>  {\n>  \tstruct merge_options_internal *opti = result->priv;\n>  \tstruct hashmap_iter iter;\n> @@ -4263,7 +4264,7 @@ void merge_display_update_messages(struct merge_options *opt,\n>  \tfor (i = 0; i < olist.nr; ++i) {\n>  \t\tstruct strbuf *sb = olist.items[i].util;\n>\n> -\t\tprintf(\"%s\", sb->buf);\n> +\t\tfprintf(stream, \"%s\", sb->buf);\n\nMaybe `strbuf_write(sb, stream);` instead? Whenever I see a `\"%s\"`, I feel\nlike it's unnecessary churn...\n\n>  \t}\n>  \tstring_list_clear(&olist, 0);\n>\n\nMissing from this hunk:\n\n        /* Also include needed rename limit adjustment now */\n        diff_warn_rename_limit(\"merge.renamelimit\",\n                               opti->renames.needed_limit, 0);\n\nThis function explicitly writes to `stdout`, and will need to be adjusted,\nI think, before we can include an adjustment to this call in this patch.\n\nUnless we override `warn_routine()` (which is used inside that function),\nthat is. Which is hacky, and we would not have addressed the\n`fflush(stdout)` in `diff_warn_rename_limit()`. So I would much prefer\nsomething like this:\n\n-- snip --\ndiff --git a/diff.c b/diff.c\nindex 861282db1c3..87966602cbd 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6312,17 +6312,25 @@ static const char rename_limit_advice[] =\n N_(\"you may want to set your %s variable to at least \"\n    \"%d and retry the command.\");\n\n-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)\n+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n+\t\t\t    FILE *out)\n {\n-\tfflush(stdout);\n+\tconst char *fmt = NULL;\n+\n \tif (degraded_cc)\n-\t\twarning(_(degrade_cc_to_c_warning));\n+\t\tfmt = _(degrade_cc_to_c_warning);\n \telse if (needed)\n-\t\twarning(_(rename_limit_warning));\n+\t\tfmt = _(rename_limit_warning);\n \telse\n \t\treturn;\n \tif (0 < needed)\n-\t\twarning(_(rename_limit_advice), varname, needed);\n+\t\tfmt = _(rename_limit_advice);\n+\n+\tfflush(out);\n+\tif (out == stdout)\n+\t\twarning(fmt, varname, needed);\n+\telse\n+\t\tfprintf(out, fmt, varname, needed);\n }\n\n static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n@@ -6754,7 +6762,7 @@ int diff_result_code(struct diff_options *opt, int status)\n\n \tdiff_warn_rename_limit(\"diff.renameLimit\",\n \t\t\t       opt->needed_rename_limit,\n-\t\t\t       opt->degraded_cc_to_c);\n+\t\t\t       opt->degraded_cc_to_c, stdout);\n \tif (!opt->flags.exit_with_status &&\n \t    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))\n \t\treturn status;\ndiff --git a/diff.h b/diff.h\nindex 8ba85c5e605..be4ee68c0a2 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -596,7 +596,8 @@ void diffcore_fix_diff_index(void);\n int diff_queue_is_empty(void);\n void diff_flush(struct diff_options*);\n void diff_free(struct diff_options*);\n-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);\n+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n+\t\t\t    FILE *out);\n\n /* diff-raw status letters */\n #define DIFF_STATUS_ADDED\t\t'A'\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 0342f104836..e6b5a0e7c64 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4264,7 +4264,7 @@ void merge_switch_to_result(struct merge_options *opt,\n\n \t\t/* Also include needed rename limit adjustment now */\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n+\t\t\t\t       opti->renames.needed_limit, 0, stdout);\n\n \t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d9457797dbb..10b2948678c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3731,7 +3731,8 @@ static void merge_finalize(struct merge_options *opt)\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->priv->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0,\n+\t\t\t\t       stdout);\n \tFREE_AND_NULL(opt->priv);\n }\n\n-- snap --\n\nThe rest of the patch looks good to me.\n\nThanks,\nDscho\n\n> @@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,\n>  \t}\n>\n>  \tif (display_update_msgs)\n> -\t\tmerge_display_update_messages(opt, result);\n> +\t\tmerge_display_update_messages(opt, result, stdout);\n>\n>  \tmerge_finalize(opt, result);\n>  }\n> diff --git a/merge-ort.h b/merge-ort.h\n> index e5aec45b18f..d643b47cb7c 100644\n> --- a/merge-ort.h\n> +++ b/merge-ort.h\n> @@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,\n>   * so only call this when bypassing merge_switch_to_result().\n>   */\n>  void merge_display_update_messages(struct merge_options *opt,\n> -\t\t\t\t   struct merge_result *result);\n> +\t\t\t\t   struct merge_result *result,\n> +\t\t\t\t   FILE *stream);\n>\n>  /* Do needed cleanup when not calling merge_switch_to_result() */\n>  void merge_finalize(struct merge_options *opt,\n> --\n> gitgitgadget\n>\n>\n"},{"id":"447222","messageId":"nycvar.QRO.7.76.6.2201281731240.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"2f296aeeefbf8340cfb8b7fa4fef5ad49c8b4aa1.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 07/12] merge-tree: support including merge messages in output","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T16:37:30Z","receivedAt":"2022-01-28T16:37:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> When running `git merge-tree --write-tree`, we previously would only\n> return an exit status reflecting the cleanness of a merge, and print out\n> the toplevel tree of the resulting merge.  Merges also have\n> informational messages, (\"Auto-merging <PATH>\", \"CONFLICT (content):\n> ...\", \"CONFLICT (file/directory)\", etc.)  In fact, when non-content\n> conflicts occur (such as file/directory, modify/delete, add/add with\n> differing modes, rename/rename (1to2), etc.), these informational\n> messages are often the only notification since these conflicts are not\n> representable in the contents of the file.\n>\n> Add a --[no-]messages option so that callers can request these messages\n> be included at the end of the output.  Include such messages by default\n> when there are conflicts, and omit them by default when the merge is\n> clean.\n\nMakes sense.\n\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 0c19639594d..560640ad911 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,\n>  \t\tcommit_list_insert(j->item, &merge_bases);\n>\n>  \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> -\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> +\n>  \tif (result.clean < 0)\n>  \t\tdie(_(\"failure to merge\"));\n> -\telse if (!result.clean)\n> -\t\tprintf(_(\"Conflicts!\\n\"));\n> +\n> +\tif (o->show_messages == -1)\n> +\t\to->show_messages = !result.clean;\n> +\n> +\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> +\tif (o->show_messages) {\n> +\t\tprintf(\"\\n\");\n> +\t\tmerge_display_update_messages(&opt, &result, stdout);\n> +\t}\n\nExcellent.\n\n>  \tmerge_finalize(&opt, &result);\n>  \treturn !result.clean; /* result.clean < 0 handled above */\n>  }\n>\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -\tstruct merge_tree_options o = { 0 };\n> +\tstruct merge_tree_options o = { .show_messages = -1 };\n>  \tint expected_remaining_argc;\n> +\tint original_argc;\n>\n>  \tconst char * const merge_tree_usage[] = {\n> -\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> +\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n>  \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>  \t\tNULL\n>  \t};\n> @@ -464,6 +473,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  \t\t\t N_(\"do a real merge instead of a trivial merge\")),\n>  \t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n>  \t\t\t N_(\"do a trivial merge only\")),\n> +\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n> +\t\t\t N_(\"also show informational/conflict messages\")),\n>  \t\tOPT_END()\n>  \t};\n>\n> @@ -472,10 +483,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(merge_tree_usage, mt_options);\n>\n>  \t/* Parse arguments */\n> +\toriginal_argc = argc;\n>  \targc = parse_options(argc, argv, prefix, mt_options,\n>  \t\t\t     merge_tree_usage, 0);\n>  \tif (o.real && o.trivial)\n>  \t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n> +\tif (!o.real && original_argc < argc)\n> +\t\tdie(_(\"--write-tree must be specified if any other options are\"));\n\nHmm. Well. Hmm.\n\n\nI'd rather keep `--write-tree` neat and optional. What's wrong with\nallowing\n\n\tgit merge-tree --no-messages HEAD MERGE_HEAD\n\n?\n\nTo be clear, I think we need this instead:\n\n\tif (o.trivial && o.show_messages >= 0)\n\t\tdie(_(\"--trivial-merge is incompatible with additional options\"));\n\nI like the rest of the patch very much!\n\nThank you,\nDscho\n\n>  \tif (o.real || o.trivial) {\n>  \t\texpected_remaining_argc = (o.real ? 2 : 3);\n>  \t\tif (argc != expected_remaining_argc)\n> diff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\n> index e03688515c5..c34f8e6c1ed 100755\n> --- a/t/t4301-merge-tree-real.sh\n> +++ b/t/t4301-merge-tree-real.sh\n> @@ -84,4 +84,25 @@ test_expect_success 'Barf on too many arguments' '\n>  \tgrep \"^usage: git merge-tree\" expect\n>  '\n>\n> +test_expect_success 'test conflict notices and such' '\n> +\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n> +\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n> +\n> +\t# Expected results:\n> +\t#   \"greeting\" should merge with conflicts\n> +\t#   \"numbers\" should merge cleanly\n> +\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n> +\tcat <<-EOF >expect &&\n> +\tHASH\n> +\n> +\tAuto-merging greeting\n> +\tCONFLICT (content): Merge conflict in greeting\n> +\tAuto-merging numbers\n> +\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n> +\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n> +\tEOF\n> +\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_done\n> --\n> gitgitgadget\n>\n>\n"},{"id":"447223","messageId":"nycvar.QRO.7.76.6.2201281744280.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"35e0ed9271a0229fe2acd2385a7e4171d4dfe077.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T16:55:07Z","receivedAt":"2022-01-28T16:55:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> After a merge, this function allows the user to extract the same\n> information that would be printed by `ls-files -u` -- conflicted\n> files with their mode, oid, and stage.\n\nHmm. Okay.\n\nI am not really a fan of that output where we use a variable\nnumber of lines per file. As in: in the regular case, where a file was\nmodified in divergent ways, we get three lines. But if it was deleted in\none branch, or if it was created in both branches, we have only two lines.\n\nFrankly, I'd much rather have 3 lines for each and every conflict.\n\nGranted, we currently only have three stages, and we can pretty much\nguarantee that at least two of these stages are non-empty (otherwise where\nwould be the conflict?).\n\nMeaning: Even if stage 3 is missing from the first conflict and stage 1 is\nmissing from the second conflict, in the output we would see stages 1, 2,\n2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\ndifferent conflicts.\n\nBut still. It makes me uneasy to have that much variability in\nmachine-consumable output.\n\nAnd who knows, maybe if we're ever implementing more sophisticated merge\nstrategies for octopus merges, we might end up with more stages...\n\nIn other words...\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-ort.c | 31 +++++++++++++++++++++++++++++++\n>  merge-ort.h | 21 +++++++++++++++++++++\n>  2 files changed, 52 insertions(+)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index b78dde55ad9..5e7cea6cc8f 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,\n>  \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n>  }\n>\n> +void merge_get_conflicted_files(struct merge_result *result,\n> +\t\t\t\tstruct string_list *conflicted_files)\n> +{\n> +\tstruct hashmap_iter iter;\n> +\tstruct strmap_entry *e;\n> +\tstruct merge_options_internal *opti = result->priv;\n> +\n> +\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n> +\t\tconst char *path = e->key;\n> +\t\tstruct conflict_info *ci = e->value;\n> +\t\tint i;\n> +\n> +\t\tVERIFY_CI(ci);\n> +\n> +\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n> +\t\t\tstruct stage_info *si;\n> +\n> +\t\t\tif (!(ci->filemask & (1ul << i)))\n> +\t\t\t\tcontinue;\n> +\n> +\t\t\tsi = xmalloc(sizeof(*si));\n> +\t\t\tsi->stage = i+1;\n> +\t\t\tsi->mode = ci->stages[i].mode;\n> +\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n> +\t\t\tstring_list_append(conflicted_files, path)->util = si;\n> +\t\t}\n> +\t}\n> +\t/* string_list_sort() uses a stable sort, so we're good */\n> +\tstring_list_sort(conflicted_files);\n> +}\n> +\n>  void merge_switch_to_result(struct merge_options *opt,\n>  \t\t\t    struct tree *head,\n>  \t\t\t    struct merge_result *result,\n> diff --git a/merge-ort.h b/merge-ort.h\n> index d643b47cb7c..e635a294ea8 100644\n> --- a/merge-ort.h\n> +++ b/merge-ort.h\n> @@ -2,6 +2,7 @@\n>  #define MERGE_ORT_H\n>\n>  #include \"merge-recursive.h\"\n> +#include \"hash.h\"\n>\n>  struct commit;\n>  struct tree;\n> @@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,\n>  \t\t\t\t   struct merge_result *result,\n>  \t\t\t\t   FILE *stream);\n>\n> +struct stage_info {\n> +\tstruct object_id oid;\n> +\tint mode;\n> +\tint stage;\n> +};\n\n... I'd rather not tack this onto a `string_list` but instead have\nsomething like:\n\n\tstruct conflict_info {\n\t\tstruct {\n\t\t\tstruct object_id oid;\n\t\t\tint mode;\n\t\t\tconst char *path;\n\t\t} stages[3]\n\t};\n\nwhere `path` can be `NULL` to indicate that this stage is missing.\n\nApart from this concern about the overall design, the patch looks good, of\ncourse.\n\nCiao,\nDscho\n\n> +\n> +/*\n> + * Provide a list of path -> {struct stage_info*} mappings for\n> + * all conflicted files.  Note that each path could appear up to three\n> + * times in the list, corresponding to 3 different stage entries.  In short,\n> + * this basically provides the info that would be printed by `ls-files -u`.\n> + *\n> + * result should have been populated by a call to\n> + * one of the merge_incore_[non]recursive() functions.\n> + *\n> + * conflicted_files should be empty before calling this function.\n> + */\n> +void merge_get_conflicted_files(struct merge_result *result,\n> +\t\t\t\tstruct string_list *conflicted_files);\n> +\n>  /* Do needed cleanup when not calling merge_switch_to_result() */\n>  void merge_finalize(struct merge_options *opt,\n>  \t\t    struct merge_result *result);\n> --\n> gitgitgadget\n>\n>\n"},{"id":"447224","messageId":"nycvar.QRO.7.76.6.2201281755200.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"fcbb087fa8865ac05e20473d822cd9795590ee38.1642888562.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T16:57:01Z","receivedAt":"2022-01-28T16:57:10Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Callers of `git merge-tree --write-tree` will often want to know which\n> files had conflicts.  While they could potentially attempt to parse the\n> CONFLICT notices printed, those messages are not meant to be machine\n> readable.  Provide a simpler mechanism of just printing the files (in\n> the same format as `git ls-files` with quoting, but restricted to\n> unmerged files) in the output before the free-form messages.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  Documentation/git-merge-tree.txt |  8 ++++++++\n>  builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n>  t/t4301-merge-tree-real.sh       | 11 +++++++++++\n>  3 files changed, 41 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index fd7a867de60..041a4ac2785 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -58,6 +58,7 @@ simply one line:\n>  Whereas for a conflicted merge, the output is by default of the form:\n>\n>  \t<OID of toplevel tree>\n> +\t<Conflicted file list>\n>  \t<Informational messages>\n\nTo distinguish between the list of conflicted files and the informational\nmessages, I think it would be good to insert an empty line, as a\nseparator, like. And...\n\n>\n>  These are discussed individually below.\n> @@ -69,6 +70,13 @@ This is a tree object that represents what would be checked out in the\n>  working tree at the end of `git merge`.  If there were conflicts, then\n>  files within this tree may have embedded conflict markers.\n>\n> +Conflicted file list\n> +~~~~~~~~~~~~~~~~~~~~\n> +\n> +This is a sequence of lines containing a filename on each line, quoted\n> +as explained for the configuration variable `core.quotePath` (see\n> +linkgit:git-config[1]).\n> +\n>  Informational messages\n>  ~~~~~~~~~~~~~~~~~~~~~~\n>\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 560640ad911..d8eeeb3f306 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -11,6 +11,9 @@\n>  #include \"blob.h\"\n>  #include \"exec-cmd.h\"\n>  #include \"merge-blobs.h\"\n> +#include \"quote.h\"\n> +\n> +static int line_termination = '\\n';\n>\n>  struct merge_list {\n>  \tstruct merge_list *next;\n> @@ -395,7 +398,8 @@ struct merge_tree_options {\n>  };\n>\n>  static int real_merge(struct merge_tree_options *o,\n> -\t\t      const char *branch1, const char *branch2)\n> +\t\t      const char *branch1, const char *branch2,\n> +\t\t      const char *prefix)\n>  {\n>  \tstruct commit *parent1, *parent2;\n>  \tstruct commit_list *common;\n> @@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,\n>  \t\to->show_messages = !result.clean;\n>\n>  \tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> +\tif (!result.clean) {\n> +\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n> +\t\tconst char *last = NULL;\n> +\t\tint i;\n> +\n> +\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n> +\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n> +\t\t\tconst char *name = conflicted_files.items[i].string;\n> +\t\t\tif (last && !strcmp(last, name))\n> +\t\t\t\tcontinue;\n> +\t\t\twrite_name_quoted_relative(\n> +\t\t\t\tname, prefix, stdout, line_termination);\n> +\t\t\tlast = name;\n> +\t\t}\n> +\t\tstring_list_clear(&conflicted_files, 1);\n> +\t}\n>  \tif (o->show_messages) {\n>  \t\tprintf(\"\\n\");\n\n... it seems that we do this already...\n\n>  \t\tmerge_display_update_messages(&opt, &result, stdout);\n> @@ -502,7 +522,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>\n>  \t/* Do the relevant type of merge */\n>  \tif (o.real)\n> -\t\treturn real_merge(&o, argv[0], argv[1]);\n> +\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n>  \telse\n>  \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n>  }\n> diff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\n> index c34f8e6c1ed..43c9950dedb 100755\n> --- a/t/t4301-merge-tree-real.sh\n> +++ b/t/t4301-merge-tree-real.sh\n> @@ -94,6 +94,8 @@ test_expect_success 'test conflict notices and such' '\n>  \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n>  \tcat <<-EOF >expect &&\n>  \tHASH\n> +\tgreeting\n> +\twhatever~side1\n>\n>  \tAuto-merging greeting\n>  \tCONFLICT (content): Merge conflict in greeting\n\n... as illustrated by the test, too. I guess the documentation should show\nthe empty line, too?\n\nCiao,\nDscho\n\n> @@ -105,4 +107,13 @@ test_expect_success 'test conflict notices and such' '\n>  \ttest_cmp expect actual\n>  '\n>\n> +test_expect_success 'Just the conflicted files without the messages' '\n> +\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n> +\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n> +\n> +\ttest_write_lines HASH greeting whatever~side1 >expect &&\n> +\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_done\n> --\n> gitgitgadget\n>\n>\n"},{"id":"447225","messageId":"nycvar.QRO.7.76.6.2201281759120.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-28T17:00:55Z","receivedAt":"2022-01-28T17:01:04Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n\n> Note1: Depends on en/remerge-diff (but no need to pick it up; it's still\n> RFC).\n\nFor reasons, I will have to backport this on top of v2.33.1 ;-)\n\n> == Updates Log ==\n>\n> Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for\n> suggestions and comments on v2):\n>\n>  * Significant changes to output format:\n>    * Flags no longer take a filename for additional output; they write to\n>      stdout instead.\n>    * More information included by default when there are conflicts (no need\n>      to request it with additional flags, instead flags can be used to\n>      suppress it).\n>    * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n>      information -- when there are conflicts. Add a flag to only list\n>      conflicted files if that's preferred.\n>  * Much more thorough manual for git-merge-tree.txt\n>  * Renamed option from --real to --write-tree\n>  * Accept an optional --trivial-merge option to get old style merge-tree\n>    behavior\n>  * Allow both --write-tree and --trivial-merge to be omitted since we can\n>    deduce which from number of arguments\n>  * Document exit code when the merge cannot be run (so we can distinguish\n>    other error cases from conflicts)\n>  * testcase cleanups: test_tick, early skip of test when using recursive\n>    backend, variable renames, etc.\n>  * various minor code cleanups\n>  * Add a new --allow-unrelated-histories option (with same meaning as the\n>    one used in git merge)\n>  * Rebased on top of en/remerge-diff to avoid a small conflict\n\nI am really happy with the way this patch series is going, and hope to be\na lot more active on it in the near future.\n\nI've read through the current iteration and left a few suggestions,\nnothing major.\n\nThank you so much for your outstanding work!\nDscho\n"},{"id":"447260","messageId":"CABPp-BHrjpEr=Pmea1Em+cntNavd_juO1+jV-OYCEs7G1qDGSw@mail.gmail.com","threadId":"57288","inReplyTo":"CAP8UFD3_Dx2aZObbUp7dHkFCp5w9mJG-s03Soz=9YDU=yE2NoQ@mail.gmail.com","subject":"Re: [PATCH 04/12] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T04:09:12Z","receivedAt":"2022-01-29T04:09:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 26, 2022 at 1:45 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > This adds the ability to perform real merges rather than just trivial\n> > merges (meaning handling three way content merges, recursive ancestor\n> > consolidation, renames, proper directory/file conflict handling, and so\n> > forth).  However, unlike `git merge`, the working tree and index are\n> > left alone and no branch is updated.\n> >\n> > The only output is:\n> >   - the toplevel resulting tree printed on stdout\n> >   - exit status of 0 (clean) or 1 (conflicts present)\n>\n> The exit status can now actually be something other than 0 and 1\n> according to the doc and code below.\n\nThanks for catching; will fix.\n\n> > +Performs a merge, but does not make any new commits and does not read\n> > +from or write to either the working tree or index.\n> > +\n> > +The first form will merge the two branches, doing a full recursive\n> > +merge with rename detection.\n>\n> Maybe this could already tell that the first form will also write a\n> tree with the result of the merge (even in case of conflict) as this\n> could help understand the reason why the associated option is called\n> '--write-tree'. It could also help to say that we call such a merge a\n> 'real' merge.\n\nMakes sense.\n\n> > The rest of this manual (other than the\n> > +next paragraph) describes the first form in more detail -- including\n> > +options, output format, exit status, and usage notes.\n>\n> >  static int real_merge(struct merge_tree_options *o,\n> >                       const char *branch1, const char *branch2)\n> >  {\n> > -       die(_(\"real merges are not yet implemented\"));\n> > +       struct commit *parent1, *parent2;\n> > +       struct commit_list *common;\n> > +       struct commit_list *merge_bases = NULL;\n> > +       struct commit_list *j;\n> > +       struct merge_options opt;\n> > +       struct merge_result result = { 0 };\n> > +\n> > +       parent1 = get_merge_parent(branch1);\n> > +       if (!parent1)\n> > +               help_unknown_ref(branch1, \"merge\",\n> > +                                _(\"not something we can merge\"));\n>\n> The second argument is supposed to be the command (it's called \"cmd\"),\n> so maybe \"merge-tree\" instead of \"merge\".\n\nOh, good catch; thanks for pointing this out.\n\n>\n> > +       parent2 = get_merge_parent(branch2);\n> > +       if (!parent2)\n> > +               help_unknown_ref(branch2, \"merge\",\n> > +                                _(\"not something we can merge\"));\n>\n> idem\n>\n> > +       opt.show_rename_progress = 0;\n> > +\n> > +       opt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */\n> > +       opt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */\n>\n> I think just:\n>\n>        opt.branch1 = branch1\n>        opt.branch2 = branch2\n>\n> might be better for users as it should show the name as it was passed\n> to the command.\n\nAfter digging for a bit, I think in this case there actually isn't a\ndifference to users because both will give the same result.  But, if\nthat's the case, the simpler code is warranted.\n\n> > +       merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> > +       printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n>\n> I wonder if we can actually always output a valid tree when\n> result.clean < 0. In case we might not, the printing should go a few\n> lines below.\n\nYeah, I caught that and fixed it, but got it squashed into a later\ncommit.  I'll fix it up.\n\n> > +       if (result.clean < 0)\n> > +               die(_(\"failure to merge\"));\n> > +       else if (!result.clean)\n>\n> The \"else\" is not necessary above.\n>\n> > +               printf(_(\"Conflicts!\\n\"));\n\nYes, and the else clause should just be ripped out.\n\n> > +       merge_finalize(&opt, &result);\n> > +       return !result.clean; /* result.clean < 0 handled above */\n> >  }\n>\n> > diff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\n> > new file mode 100755\n> > index 00000000000..e03688515c5\n> > --- /dev/null\n> > +++ b/t/t4301-merge-tree-real.sh\n>\n> I wonder if it would be better named 't4301-merge-tree-write-tree.sh'...\n>\n> > @@ -0,0 +1,87 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='git merge-tree --write-tree'\n>\n> ... especially given this description.\n\nMakes sense; will rename.\n"},{"id":"447261","messageId":"CABPp-BGHf864FMaS0YkBUF=Rr9gmrQYVav96nwMmYWFVs3XuFA@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2201281716420.347@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 06/12] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T04:33:20Z","receivedAt":"2022-01-29T04:33:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jan 28, 2022 at 8:31 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > diff --git a/merge-ort.c b/merge-ort.c\n> > index f9e35b0f96b..b78dde55ad9 100644\n> > --- a/merge-ort.c\n> > +++ b/merge-ort.c\n> > @@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n> >  }\n> >\n> >  void merge_display_update_messages(struct merge_options *opt,\n> > -                                struct merge_result *result)\n> > +                                struct merge_result *result,\n> > +                                FILE *stream)\n> >  {\n> >       struct merge_options_internal *opti = result->priv;\n> >       struct hashmap_iter iter;\n> > @@ -4263,7 +4264,7 @@ void merge_display_update_messages(struct merge_options *opt,\n> >       for (i = 0; i < olist.nr; ++i) {\n> >               struct strbuf *sb = olist.items[i].util;\n> >\n> > -             printf(\"%s\", sb->buf);\n> > +             fprintf(stream, \"%s\", sb->buf);\n>\n> Maybe `strbuf_write(sb, stream);` instead? Whenever I see a `\"%s\"`, I feel\n> like it's unnecessary churn...\n\nMakes sense.\n\n> >       }\n> >       string_list_clear(&olist, 0);\n> >\n>\n> Missing from this hunk:\n>\n>         /* Also include needed rename limit adjustment now */\n>         diff_warn_rename_limit(\"merge.renamelimit\",\n>                                opti->renames.needed_limit, 0);\n>\n> This function explicitly writes to `stdout`, and will need to be adjusted,\n> I think, before we can include an adjustment to this call in this patch.\n\nOh, good point.\n\n> Unless we override `warn_routine()` (which is used inside that function),\n> that is. Which is hacky, and we would not have addressed the\n> `fflush(stdout)` in `diff_warn_rename_limit()`. So I would much prefer\n> something like this:\n>\n> -- snip --\n> diff --git a/diff.c b/diff.c\n> index 861282db1c3..87966602cbd 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -6312,17 +6312,25 @@ static const char rename_limit_advice[] =\n>  N_(\"you may want to set your %s variable to at least \"\n>     \"%d and retry the command.\");\n>\n> -void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)\n> +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n> +                           FILE *out)\n>  {\n> -       fflush(stdout);\n> +       const char *fmt = NULL;\n> +\n>         if (degraded_cc)\n> -               warning(_(degrade_cc_to_c_warning));\n> +               fmt = _(degrade_cc_to_c_warning);\n>         else if (needed)\n> -               warning(_(rename_limit_warning));\n> +               fmt = _(rename_limit_warning);\n>         else\n>                 return;\n>         if (0 < needed)\n> -               warning(_(rename_limit_advice), varname, needed);\n> +               fmt = _(rename_limit_advice);\n> +\n> +       fflush(out);\n> +       if (out == stdout)\n> +               warning(fmt, varname, needed);\n> +       else\n> +               fprintf(out, fmt, varname, needed);\n>  }\n>\n>  static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n> @@ -6754,7 +6762,7 @@ int diff_result_code(struct diff_options *opt, int status)\n>\n>         diff_warn_rename_limit(\"diff.renameLimit\",\n>                                opt->needed_rename_limit,\n> -                              opt->degraded_cc_to_c);\n> +                              opt->degraded_cc_to_c, stdout);\n>         if (!opt->flags.exit_with_status &&\n>             !(opt->output_format & DIFF_FORMAT_CHECKDIFF))\n>                 return status;\n> diff --git a/diff.h b/diff.h\n> index 8ba85c5e605..be4ee68c0a2 100644\n> --- a/diff.h\n> +++ b/diff.h\n> @@ -596,7 +596,8 @@ void diffcore_fix_diff_index(void);\n>  int diff_queue_is_empty(void);\n>  void diff_flush(struct diff_options*);\n>  void diff_free(struct diff_options*);\n> -void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);\n> +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n> +                           FILE *out);\n>\n>  /* diff-raw status letters */\n>  #define DIFF_STATUS_ADDED              'A'\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 0342f104836..e6b5a0e7c64 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -4264,7 +4264,7 @@ void merge_switch_to_result(struct merge_options *opt,\n>\n>                 /* Also include needed rename limit adjustment now */\n>                 diff_warn_rename_limit(\"merge.renamelimit\",\n> -                                      opti->renames.needed_limit, 0);\n> +                                      opti->renames.needed_limit, 0, stdout);\n>\n>                 trace2_region_leave(\"merge\", \"display messages\", opt->repo);\n>         }\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index d9457797dbb..10b2948678c 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -3731,7 +3731,8 @@ static void merge_finalize(struct merge_options *opt)\n>                 strbuf_release(&opt->obuf);\n>         if (show(opt, 2))\n>                 diff_warn_rename_limit(\"merge.renamelimit\",\n> -                                      opt->priv->needed_rename_limit, 0);\n> +                                      opt->priv->needed_rename_limit, 0,\n> +                                      stdout);\n>         FREE_AND_NULL(opt->priv);\n>  }\n>\n> -- snap --\n\nI like this, but believe it makes more sense as a preparatory patch.\nSo I created one and made you the author, and included your\nsigned-off-by.  That okay with you?\n\n>\n> The rest of the patch looks good to me.\n\nThanks!\n"},{"id":"447262","messageId":"CABPp-BEnH-DeQnReUSCg+CH4ZZr-GzPogCnQvZ76po_BzEPjuw@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2201281731240.347@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 07/12] merge-tree: support including merge messages in output","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T04:46:46Z","receivedAt":"2022-01-29T04:47:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jan 28, 2022 at 8:37 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > When running `git merge-tree --write-tree`, we previously would only\n> > return an exit status reflecting the cleanness of a merge, and print out\n> > the toplevel tree of the resulting merge.  Merges also have\n> > informational messages, (\"Auto-merging <PATH>\", \"CONFLICT (content):\n> > ...\", \"CONFLICT (file/directory)\", etc.)  In fact, when non-content\n> > conflicts occur (such as file/directory, modify/delete, add/add with\n> > differing modes, rename/rename (1to2), etc.), these informational\n> > messages are often the only notification since these conflicts are not\n> > representable in the contents of the file.\n> >\n> > Add a --[no-]messages option so that callers can request these messages\n> > be included at the end of the output.  Include such messages by default\n> > when there are conflicts, and omit them by default when the merge is\n> > clean.\n>\n> Makes sense.\n>\n> > diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> > index 0c19639594d..560640ad911 100644\n> > --- a/builtin/merge-tree.c\n> > +++ b/builtin/merge-tree.c\n> > @@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,\n> >               commit_list_insert(j->item, &merge_bases);\n> >\n> >       merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> > -     printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> > +\n> >       if (result.clean < 0)\n> >               die(_(\"failure to merge\"));\n> > -     else if (!result.clean)\n> > -             printf(_(\"Conflicts!\\n\"));\n> > +\n> > +     if (o->show_messages == -1)\n> > +             o->show_messages = !result.clean;\n> > +\n> > +     printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> > +     if (o->show_messages) {\n> > +             printf(\"\\n\");\n> > +             merge_display_update_messages(&opt, &result, stdout);\n> > +     }\n>\n> Excellent.\n>\n> >       merge_finalize(&opt, &result);\n> >       return !result.clean; /* result.clean < 0 handled above */\n> >  }\n> >\n> >  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >  {\n> > -     struct merge_tree_options o = { 0 };\n> > +     struct merge_tree_options o = { .show_messages = -1 };\n> >       int expected_remaining_argc;\n> > +     int original_argc;\n> >\n> >       const char * const merge_tree_usage[] = {\n> > -             N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> > +             N_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n> >               N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> >               NULL\n> >       };\n> > @@ -464,6 +473,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >                        N_(\"do a real merge instead of a trivial merge\")),\n> >               OPT_BOOL(0, \"trivial-merge\", &o.trivial,\n> >                        N_(\"do a trivial merge only\")),\n> > +             OPT_BOOL(0, \"messages\", &o.show_messages,\n> > +                      N_(\"also show informational/conflict messages\")),\n> >               OPT_END()\n> >       };\n> >\n> > @@ -472,10 +483,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >               usage_with_options(merge_tree_usage, mt_options);\n> >\n> >       /* Parse arguments */\n> > +     original_argc = argc;\n> >       argc = parse_options(argc, argv, prefix, mt_options,\n> >                            merge_tree_usage, 0);\n> >       if (o.real && o.trivial)\n> >               die(_(\"--write-tree and --trivial-merge are incompatible\"));\n> > +     if (!o.real && original_argc < argc)\n> > +             die(_(\"--write-tree must be specified if any other options are\"));\n>\n> Hmm. Well. Hmm.\n>\n>\n> I'd rather keep `--write-tree` neat and optional. What's wrong with\n> allowing\n>\n>         git merge-tree --no-messages HEAD MERGE_HEAD\n>\n> ?\n\nYeah, oops.  Nothing is wrong with that...but do note that there is\nsomething wrong with this:\n\n    git merge-tree --no-messages MERGE_BASE HEAD MERGE_HEAD\n\nbecause the three argument form is signalling the deprecated trivial\nmerge, and the trivial merge code doesn't handle any options.  You\nwere right to call out my code since I placed it too early and made it\na bit obtuse.  I've changed it to:\n\n    if (o.mode == 't' && original_argc < argc)\n        die(_(\"--trivial-merge is incompatible with all other options\"));\n\nwhere o.mode is guaranteed to be set before that check.\n\n> To be clear, I think we need this instead:\n>\n>         if (o.trivial && o.show_messages >= 0)\n>                 die(_(\"--trivial-merge is incompatible with additional options\"));\n\nMost of that is better, assuming the o.trivial check handles the\nimplicit case too.  Thanks.  I'm not so sure about the check on\no.show_messages, though, because then I'll have to keep adding\nadditional checks for each new additional option (and future patches\nwill add more).  So, I think I'll keep the original_argc < argc part\nof the check instead of that.  :-)\n\n> I like the rest of the patch very much!\n\nThanks!\n"},{"id":"447263","messageId":"CABPp-BGVCdSoVowgmM17xCiuYLwfGVLvLm=Ue8tPB+q+N3oB6g@mail.gmail.com","threadId":"57288","inReplyTo":"CAP8UFD0fRTw0Uh6oWNtCdotRb3F6fvZnpUwm=vT0qzmfeuBvEQ@mail.gmail.com","subject":"Re: [PATCH 07/12] merge-tree: support including merge messages in output","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T04:52:15Z","receivedAt":"2022-01-29T04:52:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 26, 2022 at 2:42 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> >  EXIT STATUS\n> >  -----------\n> > @@ -72,7 +102,8 @@ be used as a part of a series of steps such as\n> >\n> >  However, it does not quite fit into the same category of low-level\n> >  plumbing commands since the possibility of merge conflicts give it a\n> > -much higher chance of the command not succeeding.\n> > +much higher chance of the command not succeeding (and NEWTREE containing\n> > +a bunch of stuff other than just a toplevel tree).\n>\n> Is this hunk really related to this commit or should it go into a\n> previous commit?\n\nIt's meant to first be related to this commit, though as you pointed\nout below, I had some accidental stuff not cleaned out of the earlier\ncommit.\n\n> > @@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,\n> >                 commit_list_insert(j->item, &merge_bases);\n> >\n> >         merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> > -       printf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n> > +\n> >         if (result.clean < 0)\n> >                 die(_(\"failure to merge\"));\n>\n> So this addresses the comment I made in a previous commit related to\n> the fact that if result.clean < 0 we might not have a valid tree that\n> we can print. I think though that it would be better if that was\n> addressed in a previous commit.\n>\n> > -       else if (!result.clean)\n> > -               printf(_(\"Conflicts!\\n\"));\n>\n> Ok, so we don't print \"Conflicts!\\n\" now, which makes me wonder if we\n> should have printed it in the first place in previous commits.\n\nYep, good flag on both of these last two comments.  When I was fixing\nthis up I didn't squash it back in early enough.  Thanks for reading\ncarefully.\n\n> >         if (o.real && o.trivial)\n> >                 die(_(\"--write-tree and --trivial-merge are incompatible\"));\n> > +       if (!o.real && original_argc < argc)\n> > +               die(_(\"--write-tree must be specified if any other options are\"));\n>\n> Is this necessary? It looks to me like another thing that would be\n> simplified if we were just adding a new command...\n\nI think the code is harder to read than it should be.  I changed it to:\n\n    if (o.mode == 't' && original_argc < argc)\n        die(_(\"--trivial-merge is incompatible with all other options\"));\n\nwhich I think is clearer; it points out that it's only about the\ndeprecated --trivial-merge option and how it's incompatible with all\nother options.\n"},{"id":"447264","messageId":"CABPp-BFPDLQ8SQ3QhM+KPpkyKatckroBrGQJugpZ+z5CUwD_JA@mail.gmail.com","threadId":"57288","inReplyTo":"CAP8UFD0+ui2uJtyNSQ3Cq4k+9SPpjT08=CBhMFNJ0ni2tLQQPw@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T04:55:13Z","receivedAt":"2022-01-29T04:55:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 26, 2022 at 2:55 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> > After a merge, this function allows the user to extract the same\n> > information that would be printed by `ls-files -u` -- conflicted\n>\n> This made me wonder if \"-- conflicted\" should be part of the `ls-files\n> -u` command. Maybe \"by `ls-files -u`, which means\" would make things a\n> bit clearer.\n>\n> > files with their mode, oid, and stage.\n\nYes, I like that wording better.  Thanks!\n"},{"id":"447265","messageId":"CABPp-BFR+VgAWUf+eNVUkBO8uv9ipHk2vzS_=m9QHmzH3pE0AQ@mail.gmail.com","threadId":"57288","inReplyTo":"CAP8UFD0iQg4nL6eSTDbEu8t6h+K0H+nGF8y_N0z3XyjH+KGORA@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T05:06:28Z","receivedAt":"2022-01-29T05:06:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 26, 2022 at 3:07 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> > +void merge_get_conflicted_files(struct merge_result *result,\n> > +                               struct string_list *conflicted_files)\n> > +{\n> > +       struct hashmap_iter iter;\n> > +       struct strmap_entry *e;\n> > +       struct merge_options_internal *opti = result->priv;\n> > +\n> > +       strmap_for_each_entry(&opti->conflicted, &iter, e) {\n> > +               const char *path = e->key;\n> > +               struct conflict_info *ci = e->value;\n> > +               int i;\n> > +\n> > +               VERIFY_CI(ci);\n> > +\n> > +               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n> > +                       struct stage_info *si;\n> > +\n> > +                       if (!(ci->filemask & (1ul << i)))\n> > +                               continue;\n> > +\n> > +                       si = xmalloc(sizeof(*si));\n>\n> It's probably a premature optimization, so feel free to ignore, but as\n> MERGE_BASE and MERGE_SIDE2 are constants, and ci->filemask is constant\n> inside the 'for' loop, we could compute before the 'for' loop how many\n> 'struct stage_info' we will need and allocate them all at once before\n> the 'for' loop.\n\nThat's an interesting idea, but if we allocate all at once, then we'd\nneed to be able to deallocate all at once.  I'm not sure how to do\nthat, but basically the straightforward\n    string_list_free(conflicted_files, 1);\nthat callers can use with the current function would no longer be\nvalid, and it might be somewhat difficult for callers to figure out\nhow to free the memory that was allocated.\n"},{"id":"447266","messageId":"CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2201281744280.347@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T06:08:05Z","receivedAt":"2022-01-29T06:08:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > After a merge, this function allows the user to extract the same\n> > information that would be printed by `ls-files -u` -- conflicted\n> > files with their mode, oid, and stage.\n>\n> Hmm. Okay.\n>\n> I am not really a fan of that output where we use a variable\n> number of lines per file. As in: in the regular case, where a file was\n> modified in divergent ways, we get three lines. But if it was deleted in\n> one branch, or if it was created in both branches, we have only two lines.\n>\n> Frankly, I'd much rather have 3 lines for each and every conflict.\n\nOut of curiosity, does this also mean you feel like `git ls-files -u`\nis mis-designed (even if we're stuck with it for backward\ncompatibility reasons)?\n\nAnyway, if we make this change guaranteeing there are 3 lines for each\nand every conflict, that probably implies we can remove the stage\nfield (just letting it be implicit), right?  And it also implies that\nthe \"path\" field of the lines is now duplicate information.  Should\nthe path be printed on each line regardless of the duplication?\nShould the path be printed separately on its own line, followed by the\nthree lines of (mode, oid) pairs?  Or should the format be something\nelse entirely?\n\n> Granted, we currently only have three stages...\n\nI thought that was true too, but Junio informed me otherwise[1].\nGranted, ort does not currently make use of that, but it could.  Not\nsure I want to rule it out.\n\n[1] https://lore.kernel.org/git/xmqqr1t89gi8.fsf@gitster.c.googlers.com/\n\n> ...and we can pretty much\n> guarantee that at least two of these stages are non-empty (otherwise where\n> would be the conflict?).\n\nNo, that's not true.  See the paragraph about \"conflict types\" from\nthe final patch in this series, where I mentioned three different\ntypes of conflicts that can result in a path having a single higher\norder stage.\n\n> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n> missing from the second conflict, in the output we would see stages 1, 2,\n> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n> different conflicts.\n\nI don't understand why you're fixating on the stage here.  Why would\nyou want to group all the stage 2s together, count them up, and then\ndetermine there are N conflicting files because there are N stage 2's?\n In such a design, you'd have to do the extra post-processing to\nrecognize the all-zero modes and hashes and toss them.\n\nTo me, that seems like more work than just handling the `ls-files -u`\nstyle of output, and doesn't have the advantage of showing things in a\nformat users may already be familiar with (see above about dropping\nstages and maybe also pathname).  Since the `ls-files -u` output is\nsimply several lines of:\n   <mode> <hash> <stage> <filename>\nYou can get the number of conflicted files by counting the number of\n*unique* filenames.  If you want to see which lines are about the same\nfile, get all the lines with the same filename -- they are sorted by\n(<filename>, <stage>) for convenience, much like `ls-files -u` is.\n\n\nOn a different angle, I'm also slightly worried there's an embedded\nassumption here, one that I tried to address in the \"Mistake to avoid\"\nsection I added in my last patch of this series.  What if you have a\nconflicting merge and 0 lines of output in the conflicting info\nsection?  Is there something about the reason you're asking for three\nlines of output per conflicted file which is going to make you\noverlook that particular possibility and not handle it?\n\n> But still. It makes me uneasy to have that much variability in\n> machine-consumable output.\n>\n> And who knows, maybe if we're ever implementing more sophisticated merge\n> strategies for octopus merges, we might end up with more stages...\n\nUm, if we need to handle more stages, wouldn't that undercut your\nrequest for exactly 3 stages?  Wouldn't it suggest that we would\nindeed want to have a flexible number of lines?\n\n> In other words...\n>\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n> > ---\n> >  merge-ort.c | 31 +++++++++++++++++++++++++++++++\n> >  merge-ort.h | 21 +++++++++++++++++++++\n> >  2 files changed, 52 insertions(+)\n> >\n> > diff --git a/merge-ort.c b/merge-ort.c\n> > index b78dde55ad9..5e7cea6cc8f 100644\n> > --- a/merge-ort.c\n> > +++ b/merge-ort.c\n> > @@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,\n> >       trace2_region_leave(\"merge\", \"display messages\", opt->repo);\n> >  }\n> >\n> > +void merge_get_conflicted_files(struct merge_result *result,\n> > +                             struct string_list *conflicted_files)\n> > +{\n> > +     struct hashmap_iter iter;\n> > +     struct strmap_entry *e;\n> > +     struct merge_options_internal *opti = result->priv;\n> > +\n> > +     strmap_for_each_entry(&opti->conflicted, &iter, e) {\n> > +             const char *path = e->key;\n> > +             struct conflict_info *ci = e->value;\n> > +             int i;\n> > +\n> > +             VERIFY_CI(ci);\n> > +\n> > +             for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n> > +                     struct stage_info *si;\n> > +\n> > +                     if (!(ci->filemask & (1ul << i)))\n> > +                             continue;\n> > +\n> > +                     si = xmalloc(sizeof(*si));\n> > +                     si->stage = i+1;\n> > +                     si->mode = ci->stages[i].mode;\n> > +                     oidcpy(&si->oid, &ci->stages[i].oid);\n> > +                     string_list_append(conflicted_files, path)->util = si;\n> > +             }\n> > +     }\n> > +     /* string_list_sort() uses a stable sort, so we're good */\n> > +     string_list_sort(conflicted_files);\n> > +}\n> > +\n> >  void merge_switch_to_result(struct merge_options *opt,\n> >                           struct tree *head,\n> >                           struct merge_result *result,\n> > diff --git a/merge-ort.h b/merge-ort.h\n> > index d643b47cb7c..e635a294ea8 100644\n> > --- a/merge-ort.h\n> > +++ b/merge-ort.h\n> > @@ -2,6 +2,7 @@\n> >  #define MERGE_ORT_H\n> >\n> >  #include \"merge-recursive.h\"\n> > +#include \"hash.h\"\n> >\n> >  struct commit;\n> >  struct tree;\n> > @@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,\n> >                                  struct merge_result *result,\n> >                                  FILE *stream);\n> >\n> > +struct stage_info {\n> > +     struct object_id oid;\n> > +     int mode;\n> > +     int stage;\n> > +};\n>\n> ... I'd rather not tack this onto a `string_list` but instead have\n> something like:\n>\n>         struct conflict_info {\n\nNit: \"conflict_info\" is already taken for another structure; we'd need\na different name.  But it does illustrate the idea just fine.\n\n>                 struct {\n>                         struct object_id oid;\n>                         int mode;\n>                         const char *path;\n>                 } stages[3]\n>         };\n>\n> where `path` can be `NULL` to indicate that this stage is missing.\n\nI'll get back to the data structure in a second, but the note about\npath being `NULL` is interesting.  I'm presuming that oid and mode are\nall-zeros as well, yes?  Now, your original request was that you\nwanted a line printed for all three stages.  If we're printing oid,\nmode, and path for each stage, what do we print for the path one these\nlines?  Is it (1) \"(null)\", (2) \"\", or (3) the real pathname (via\ncarefully comparing to surrounding stages to find out what it is)?  If\nwe're changing the format to only print the name of the path once,\ndoes the code need to loop over the list of conflicts in order to find\nout the name (since the first or second stage might have a NULL path\nfield)?\n\nIn regards to the overall data structure and your comment about\nstring_list: I'm slightly confused.  You say you want to avoid a\nstring_list, but you've only accounted for there being one conflict in\nthis data structure.  I don't see how this suggestion avoids the need\nfor an array or a string_list or some kind of container to hold\nmultiple of these.\n\nThe inclusion of path within the stages array is also interesting --\nshould there be (up to) three copies of the pathname allocated per\nconflict?  Seems a bit wasteful.  Wouldn't it make more sense to have\nsomething like\n\n    struct conflicts {\n        struct {\n            struct object_id oid;\n            unsigned short mode;\n        } stages[3]\n    };\n\nand then have a string_list storing the pathname -> conflicts mappings\nto avoid the pathname duplication?  Or is there something you really\nfind problematic about the string_list and you really want an array or\nother data structure?\n"},{"id":"447267","messageId":"CABPp-BEn=fvmTyYEzjSfvKkYyHj0te=6ck6WF+Jor+L1jKrVkg@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2201281755200.347@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T06:21:51Z","receivedAt":"2022-01-29T06:22:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Jan 28, 2022 at 8:57 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n[...]\n> > diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> > index fd7a867de60..041a4ac2785 100644\n> > --- a/Documentation/git-merge-tree.txt\n> > +++ b/Documentation/git-merge-tree.txt\n> > @@ -58,6 +58,7 @@ simply one line:\n> >  Whereas for a conflicted merge, the output is by default of the form:\n> >\n> >       <OID of toplevel tree>\n> > +     <Conflicted file list>\n> >       <Informational messages>\n>\n> To distinguish between the list of conflicted files and the informational\n> messages, I think it would be good to insert an empty line, as a\n> separator, like.\n\nYes, I agree; that's why I did so.  :-)\n\n[...]\n> >       if (o->show_messages) {\n> >               printf(\"\\n\");\n>\n> ... it seems that we do this already...\n\nYep.\n\n[...]\n> > diff --git a/t/t4301-merge-tree-real.sh b/t/t4301-merge-tree-real.sh\n> > index c34f8e6c1ed..43c9950dedb 100755\n> > --- a/t/t4301-merge-tree-real.sh\n> > +++ b/t/t4301-merge-tree-real.sh\n> > @@ -94,6 +94,8 @@ test_expect_success 'test conflict notices and such' '\n> >       #   \"whatever\" has *both* a modify/delete and a file/directory conflict\n> >       cat <<-EOF >expect &&\n> >       HASH\n> > +     greeting\n> > +     whatever~side1\n> >\n> >       Auto-merging greeting\n> >       CONFLICT (content): Merge conflict in greeting\n>\n> ... as illustrated by the test, too. I guess the documentation should show\n> the empty line, too?\n\nIt does:\n\n    Informational messages\n    ~~~~~~~~~~~~~~~~~~~~~~\n\n    This always starts with a blank line to separate it from the previous\n    sections, and then has free-form messages about the merge, such as:\n\nThe newline should not be split out separately, because --no-messages\nsuppresses the newline.  (Without the messages section, the newline\nisn't needed.)\n"},{"id":"447272","messageId":"CABPp-BH2sWWwy5bgn+R0hnnYQ6o0+1R1=VB-LYzDM+p4NMRhWg@mail.gmail.com","threadId":"57288","inReplyTo":"CAP8UFD1-=RDx5=JpHEp=sFEOWr2MP-YovOPE7aTydrPLoVGa5w@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T07:03:47Z","receivedAt":"2022-01-29T07:04:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jan 26, 2022 at 12:48 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n>\n> > Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for\n> > suggestions and comments on v2):\n> >\n> >  * Significant changes to output format:\n> >    * Flags no longer take a filename for additional output; they write to\n> >      stdout instead.\n> >    * More information included by default when there are conflicts (no need\n> >      to request it with additional flags, instead flags can be used to\n> >      suppress it).\n> >    * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n> >      information -- when there are conflicts. Add a flag to only list\n> >      conflicted files if that's preferred.\n>\n> The above changes seem good to me.\n\n:-)\n\n> >  * Much more thorough manual for git-merge-tree.txt\n> >  * Renamed option from --real to --write-tree\n> >  * Accept an optional --trivial-merge option to get old style merge-tree\n> >    behavior\n> >  * Allow both --write-tree and --trivial-merge to be omitted since we can\n> >    deduce which from number of arguments\n>\n> I still think that it might be simpler and cleaner to leave 'git\n> merge-tree' alone for now, and just add a new command named for\n> example 'git write-merge-tree'. Later we can always add flags to 'git\n> merge-tree' or add 'git trivial-merge-tree' as an alias for 'git\n> merge-tree', and eventually slowly switch 'git merge-tree' to mean\n> only 'git write-merge-tree' if that's where we want to go.\n\nUnderstood.  Since we can't immediately kill the old merge-tree, I\ndon't think there's a perfectly clean solution here, and it's totally\nunderstandable that different folks would have different opinions on\nwhich interim choice would be cleanest.  What you suggest is\nreasonable, I just personally prefer the path in this series.\n\n> >  * Document exit code when the merge cannot be run (so we can distinguish\n> >    other error cases from conflicts)\n> >  * testcase cleanups: test_tick, early skip of test when using recursive\n> >    backend, variable renames, etc.\n> >  * various minor code cleanups\n> >  * Add a new --allow-unrelated-histories option (with same meaning as the\n> >    one used in git merge)\n>\n> The above changes seem good to me too.\n\nThanks for reading over things carefully and for providing many\ndetailed, helpful comments!\n\n> > Stuff intentionally NOT included, but which others seemed to feel strongly\n> > about; they'd need to convince me more on these:\n> >\n> >  * Any form of diff output[1]\n>\n> It's not a big issue for me to not include them right now as long as\n> it's possible to add cli options later that add them.\n\nMy main concern is just that `merge-tree` remain a low-level tool and\nhave machine-parseable output.  I was a little worried that both you\nand Dscho wanted everything on stdout rather than in separate files,\nas the <Informational messages> part of the output is rather\nfree-form.  But since it's at the end, and has a machine-parseable\nbeginning, it can just be slurped in and we're all good.  The diff\noutput raises my eyebrow because I'm worried we're losing this\nproperty.  If there are clear usecases for adding more output, and we\ncan do so without losing this machine-parseable property, I don't have\na problem with adding an option for it.\n\nOne analogy we might use here is that `git merge` provides a diffstat\nat the end.  What you're asking is more than a diffstat, but might be\nconsidered similar-ish in nature.\n\n> The reason is\n> that I think in many cases when there are conflicts, the conflicts\n> will be small and the user will want to see them.\n\nI'm a little worried about the assumption here that conflict size is\nmeasurable and visible via diffs.  That might be true in some cases,\nbut a UI written with that assumption is going to be very confusing\nwhen hitting cases where that assumption does not hold.  For example:\n\n  * What if there is a binary file conflict, or a modify/delete or\nrename/delete conflict, or failed-to-merge submodule conflict, or a\nfile location conflict? (For these, there is no diff relative to the\nfirst parent and hence this conflict would have no diff output for\nit)?\n  * What if there was a simple file/directory conflict?  A diff would\nshow a rename (even when neither side did any renames), but not any\nconflict markers.\n  * What if there was a rename/rename conflict (both sides renamed\nsame file differently) or a distinct types conflict?  The former\nresults in three different conflicting files, none of them with\nconflict markers, while the latter results in two different\nconflicting files both without conflict markers?  Showing individual\nper-file diffs totally loses all context here -- it'll show no-diff\nfor one of the files, and totally new additions for the ones.\n\nSuch a problem statement just seems fraught with edge cases to me, and\nsuggests that the problem statement might be in need of revisiting.\n\nDon't read this as me closing the door on the possibility of diffs;\nI'm not trying to do that.  I'm listing my misgivings about how I\nthink they might be used (i.e. be careful if you're headed down this\npath as you might be digging yourself a never-ending support hole).\nYou can also think of my comments as feedback to consider and address\nwhen you propose a future feature addition for adding diffs.  If/when\nyou propose such a feature, we'd probably be able to dive more into\nspecifics and usecases at that time, which may or may not circumvent\nmy concerns.\n\n> So it would be\n> simpler to just have an option to show any conflict right away, rather\n> than have the user launch another command (a diff-tree against which\n> tree and with which options?).\n\nUm, this part I'm not sure I get.  I thought the reason for the diffs\nwas performance -- you knew you wanted the diffs, and you wanted it\ndone as part of the same process.  But why would this be simpler?\nYour patch series included three different diffs, and the emails you\npointed me at suggested all kinds of configurability.  That suggests\nthe merge-tree command would have to take the exact same options the\nuser would supply to diff, and thus would have to be told all the same\noptions, right?  I don't see how this removes any complexity at all\nfor the user.\n\nUnless...is the request in some way similar to merge's diffstat where\nthere is always a very specific type of diff that is wanted and you\naren't envisioning much flexibility in what kind of diff or what to\ndiff against -- is that where the simplification comes from?\n"},{"id":"447277","messageId":"CAP8UFD243zGGderSFtH5WxOhidAv6566Df6vdUfKRiBb1qu9tg@mail.gmail.com","threadId":"57288","inReplyTo":"CABPp-BH2sWWwy5bgn+R0hnnYQ6o0+1R1=VB-LYzDM+p4NMRhWg@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2022-01-29T08:17:55Z","receivedAt":"2022-01-29T08:18:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Sat, Jan 29, 2022 at 8:04 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Wed, Jan 26, 2022 at 12:48 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> >\n> > On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n\n> > > Stuff intentionally NOT included, but which others seemed to feel strongly\n> > > about; they'd need to convince me more on these:\n> > >\n> > >  * Any form of diff output[1]\n> >\n> > It's not a big issue for me to not include them right now as long as\n> > it's possible to add cli options later that add them.\n>\n> My main concern is just that `merge-tree` remain a low-level tool and\n> have machine-parseable output.  I was a little worried that both you\n> and Dscho wanted everything on stdout rather than in separate files,\n> as the <Informational messages> part of the output is rather\n> free-form.  But since it's at the end, and has a machine-parseable\n> beginning, it can just be slurped in and we're all good.  The diff\n> output raises my eyebrow because I'm worried we're losing this\n> property.  If there are clear usecases for adding more output, and we\n> can do so without losing this machine-parseable property, I don't have\n> a problem with adding an option for it.\n\nThat's ok for me for now. I will certainly not work on adding options\nfor diff output without any usecase.\n\n> One analogy we might use here is that `git merge` provides a diffstat\n> at the end.  What you're asking is more than a diffstat, but might be\n> considered similar-ish in nature.\n>\n> > The reason is\n> > that I think in many cases when there are conflicts, the conflicts\n> > will be small and the user will want to see them.\n>\n> I'm a little worried about the assumption here that conflict size is\n> measurable and visible via diffs.  That might be true in some cases,\n> but a UI written with that assumption is going to be very confusing\n> when hitting cases where that assumption does not hold.  For example:\n>\n>   * What if there is a binary file conflict, or a modify/delete or\n> rename/delete conflict, or failed-to-merge submodule conflict, or a\n> file location conflict? (For these, there is no diff relative to the\n> first parent and hence this conflict would have no diff output for\n> it)?\n>   * What if there was a simple file/directory conflict?  A diff would\n> show a rename (even when neither side did any renames), but not any\n> conflict markers.\n>   * What if there was a rename/rename conflict (both sides renamed\n> same file differently) or a distinct types conflict?  The former\n> results in three different conflicting files, none of them with\n> conflict markers, while the latter results in two different\n> conflicting files both without conflict markers?  Showing individual\n> per-file diffs totally loses all context here -- it'll show no-diff\n> for one of the files, and totally new additions for the ones.\n\nIn those cases we just tell users that they cannot resolve those\nconflicts in the user interface, see the following doc:\n\nhttps://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#conflicts-you-can-resolve-in-the-user-interface\n\n> Such a problem statement just seems fraught with edge cases to me, and\n> suggests that the problem statement might be in need of revisiting.\n\nUsers understand that some kinds of conflicts cannot yet be resolved\nusing a user interface. Maybe we will be able to make improvements so\nthat more kinds of conflicts can be resolved in a UI in the future\nthough. That's why a flexible and extensible output could help.\n\n> Don't read this as me closing the door on the possibility of diffs;\n> I'm not trying to do that.  I'm listing my misgivings about how I\n> think they might be used (i.e. be careful if you're headed down this\n> path as you might be digging yourself a never-ending support hole).\n> You can also think of my comments as feedback to consider and address\n> when you propose a future feature addition for adding diffs.  If/when\n> you propose such a feature, we'd probably be able to dive more into\n> specifics and usecases at that time, which may or may not circumvent\n> my concerns.\n\nI know that diffs, or any new single feature, will likely not be a\nsilver bullet.\n\n> > So it would be\n> > simpler to just have an option to show any conflict right away, rather\n> > than have the user launch another command (a diff-tree against which\n> > tree and with which options?).\n>\n> Um, this part I'm not sure I get.  I thought the reason for the diffs\n> was performance -- you knew you wanted the diffs, and you wanted it\n> done as part of the same process.  But why would this be simpler?\n\nIn the commit message of 4/12 you show an example of using it in simple scripts:\n\nNEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\ntest $? -eq 0 || die \"There were conflicts...\"\n...\n\nSo I think it would be simpler for someone interested in seeing the\nconflicts, like a script writer, or maybe someone using it manually\nfor example as a dry run before performing a merge, to be able to get\nthem right away from the command rather than to have to use another\ncommand (which means finding the right command, arguments and options\nfor that) to get them.\n\n> Your patch series included three different diffs, and the emails you\n> pointed me at suggested all kinds of configurability.  That suggests\n> the merge-tree command would have to take the exact same options the\n> user would supply to diff, and thus would have to be told all the same\n> options, right?  I don't see how this removes any complexity at all\n> for the user.\n>\n> Unless...is the request in some way similar to merge's diffstat where\n> there is always a very specific type of diff that is wanted and you\n> aren't envisioning much flexibility in what kind of diff or what to\n> diff against -- is that where the simplification comes from?\n\nWell I just think the default diff output could be tailored for the\nmost likely usecases and options made available later for more\nadvanced usecases or users.\n"},{"id":"447278","messageId":"0d7ba76c-9824-9953-b8ce-6abe810e2778@kdbg.org","threadId":"57288","inReplyTo":"CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2022-01-29T08:23:06Z","receivedAt":"2022-01-29T08:23:10Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Just a heckling from the peanut gallery...\n\nAm 29.01.22 um 07:08 schrieb Elijah Newren:\n> On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n>> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n>> missing from the second conflict, in the output we would see stages 1, 2,\n>> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n>> different conflicts.\n> \n> I don't understand why you're fixating on the stage here.  Why would\n> you want to group all the stage 2s together, count them up, and then\n> determine there are N conflicting files because there are N stage 2's?\n\nLooks like you are misunderstanding Dscho's point: When you have two\nconflicts, the first with stages 1 and 2, the second with stages 2 and\n3, then the 2s occur lumped together when the 4 lines are printed in a\nrow, and that is the cue to the parser where the new conflict begins.\nDscho did not mean that all N 2s of should be listed together.\n\n-- Hannes\n"},{"id":"447281","messageId":"CABPp-BERtRDeyF3MhOQhAFwjoykOKwXoz6635NK7j2SEKp1b3A@mail.gmail.com","threadId":"57288","inReplyTo":"0d7ba76c-9824-9953-b8ce-6abe810e2778@kdbg.org","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T16:47:41Z","receivedAt":"2022-01-29T16:47:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sat, Jan 29, 2022 at 12:23 AM Johannes Sixt <j6t@kdbg.org> wrote:\n>\n> Just a heckling from the peanut gallery...\n>\n> Am 29.01.22 um 07:08 schrieb Elijah Newren:\n> > On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> >> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n> >> missing from the second conflict, in the output we would see stages 1, 2,\n> >> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n> >> different conflicts.\n> >\n> > I don't understand why you're fixating on the stage here.  Why would\n> > you want to group all the stage 2s together, count them up, and then\n> > determine there are N conflicting files because there are N stage 2's?\n>\n> Looks like you are misunderstanding Dscho's point: When you have two\n> conflicts, the first with stages 1 and 2, the second with stages 2 and\n> 3, then the 2s occur lumped together when the 4 lines are printed in a\n> row, and that is the cue to the parser where the new conflict begins.\n> Dscho did not mean that all N 2s of should be listed together.\n\nAh, so...I didn't understand his misunderstanding?  Using stages as a\ncue to the parser where the new conflict begins is broken; you should\ninstead check for when the filename listed on a line does not match\nthe filename on the previous line.  In particular, if one conflict has\nstages 1 and 2, and the next conflict has only stage 3, then looking\nat stages only might cause you to accidentally lump unrelated\nconflicts together.\n"},{"id":"447284","messageId":"CABPp-BGYAgUbfJVMXTOTq3mMcBsFvrvRKA6KpLkcdDj7NvFEhA@mail.gmail.com","threadId":"57288","inReplyTo":"CAP8UFD243zGGderSFtH5WxOhidAv6566Df6vdUfKRiBb1qu9tg@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-29T17:43:47Z","receivedAt":"2022-01-29T17:44:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Christian,\n\nOn Sat, Jan 29, 2022 at 12:18 AM Christian Couder\n<christian.couder@gmail.com> wrote:\n>\n> On Sat, Jan 29, 2022 at 8:04 AM Elijah Newren <newren@gmail.com> wrote:\n> >\n> > On Wed, Jan 26, 2022 at 12:48 AM Christian Couder\n> > <christian.couder@gmail.com> wrote:\n> > >\n> > > On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget\n> > > <gitgitgadget@gmail.com> wrote:\n>\n> > > > Stuff intentionally NOT included, but which others seemed to feel strongly\n> > > > about; they'd need to convince me more on these:\n> > > >\n> > > >  * Any form of diff output[1]\n> > >\n> > > It's not a big issue for me to not include them right now as long as\n> > > it's possible to add cli options later that add them.\n> >\n> > My main concern is just that `merge-tree` remain a low-level tool and\n> > have machine-parseable output.  I was a little worried that both you\n> > and Dscho wanted everything on stdout rather than in separate files,\n> > as the <Informational messages> part of the output is rather\n> > free-form.  But since it's at the end, and has a machine-parseable\n> > beginning, it can just be slurped in and we're all good.  The diff\n> > output raises my eyebrow because I'm worried we're losing this\n> > property.  If there are clear usecases for adding more output, and we\n> > can do so without losing this machine-parseable property, I don't have\n> > a problem with adding an option for it.\n>\n> That's ok for me for now. I will certainly not work on adding options\n> for diff output without any usecase.\n>\n> > One analogy we might use here is that `git merge` provides a diffstat\n> > at the end.  What you're asking is more than a diffstat, but might be\n> > considered similar-ish in nature.\n> >\n> > > The reason is\n> > > that I think in many cases when there are conflicts, the conflicts\n> > > will be small and the user will want to see them.\n> >\n> > I'm a little worried about the assumption here that conflict size is\n> > measurable and visible via diffs.  That might be true in some cases,\n> > but a UI written with that assumption is going to be very confusing\n> > when hitting cases where that assumption does not hold.  For example:\n> >\n> >   * What if there is a binary file conflict, or a modify/delete or\n> > rename/delete conflict, or failed-to-merge submodule conflict, or a\n> > file location conflict? (For these, there is no diff relative to the\n> > first parent and hence this conflict would have no diff output for\n> > it)?\n> >   * What if there was a simple file/directory conflict?  A diff would\n> > show a rename (even when neither side did any renames), but not any\n> > conflict markers.\n> >   * What if there was a rename/rename conflict (both sides renamed\n> > same file differently) or a distinct types conflict?  The former\n> > results in three different conflicting files, none of them with\n> > conflict markers, while the latter results in two different\n> > conflicting files both without conflict markers?  Showing individual\n> > per-file diffs totally loses all context here -- it'll show no-diff\n> > for one of the files, and totally new additions for the ones.\n>\n> In those cases we just tell users that they cannot resolve those\n> conflicts in the user interface, see the following doc:\n>\n> https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#conflicts-you-can-resolve-in-the-user-interface\n\nSo...I think you may have just convinced me that my fears were\njustified and that I should probably NAK any attempt to add diffs to\nthe merge-tree command.  I won't jump to conclusions but you've\nprovided some pretty strong signal to me against going down that\nroute.  The list of limitations in the link you provide do mostly\navoid the broken cases I listed above, but it enshrines those\nlimitations on that webpage as fundamental rather than just as current\nimplementation shortcomings.  You may not be able to remove those\nlimitations on that webpage without either expunging the diffs from\nthe UI or exposing the brokenness of the various cases above.\n\nIf you do propose a diff option in the future, come prepared to\ndiscuss how you'll avoid accidentally leading others down into paths\nwith the same fundamental issues, and/or how the above types of\nconflicts might still be meaningfully handled.\n\nAlso, the list of limitations you have may not be quite comprehensive\nenough to avoid all problems (though it certainly avoids most of\nthem).  Can I ask a couple clarifying questions about your list of\nlimitations in that link? :\n\n  * When that page says the file cannot already contain conflict\nmarkers, is the check performed on the version of the file in the two\ntrees being merged, or is the check performed on the 2nd and 3rd index\nstage of the merge result (these are not equivalent checks, even if\nthey often give the same answer)?\n  * When that page says the file must already exist in the same path\non both branches, is the check performed on by checking the path in\nthe two trees being merged, or is the check performed on the 2nd and\n3rd index stage of the merge result (again, these are not equivalent\nchecks)?\n\n> > Such a problem statement just seems fraught with edge cases to me, and\n> > suggests that the problem statement might be in need of revisiting.\n>\n> Users understand that some kinds of conflicts cannot yet be resolved\n> using a user interface. Maybe we will be able to make improvements so\n> that more kinds of conflicts can be resolved in a UI in the future\n> though. That's why a flexible and extensible output could help.\n\nI think future improvements to handle more conflict types may well\nhinge on removing the diff-output-using portion of your interface; I\nthink it may well be fundamentally incompatible.\n\nI agree we want to leave the output format open for extension, I'm\njust saying we have to be careful about what extensions are included\nand this one worries me.\n\n> > Don't read this as me closing the door on the possibility of diffs;\n> > I'm not trying to do that.  I'm listing my misgivings about how I\n> > think they might be used (i.e. be careful if you're headed down this\n> > path as you might be digging yourself a never-ending support hole).\n> > You can also think of my comments as feedback to consider and address\n> > when you propose a future feature addition for adding diffs.  If/when\n> > you propose such a feature, we'd probably be able to dive more into\n> > specifics and usecases at that time, which may or may not circumvent\n> > my concerns.\n>\n> I know that diffs, or any new single feature, will likely not be a\n> silver bullet.\n\nSure that's fair; not being a silver bullet is fine.  We do need to\navoid providing a kryptonite bullet, though.\n\n> > > So it would be\n> > > simpler to just have an option to show any conflict right away, rather\n> > > than have the user launch another command (a diff-tree against which\n> > > tree and with which options?).\n> >\n> > Um, this part I'm not sure I get.  I thought the reason for the diffs\n> > was performance -- you knew you wanted the diffs, and you wanted it\n> > done as part of the same process.  But why would this be simpler?\n>\n> In the commit message of 4/12 you show an example of using it in simple scripts:\n>\n> NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> test $? -eq 0 || die \"There were conflicts...\"\n> ...\n>\n> So I think it would be simpler for someone interested in seeing the\n> conflicts, like a script writer, or maybe someone using it manually\n> for example as a dry run before performing a merge, to be able to get\n> them right away from the command...\n\nOkay, but the command already does that.  When there are conflicts,\nNEWTREE won't actually be a tree; it'll be lots of lines of output.\nThat's (part of) the reason for the exit status check.  So users\nalready get that information from the command.\n\n> ...rather than to have to use another\n> command (which means finding the right command, arguments and options\n> for that) to get them.\n\nAs for finding the right arguments and options...\n\n> > Your patch series included three different diffs, and the emails you\n> > pointed me at suggested all kinds of configurability.  That suggests\n> > the merge-tree command would have to take the exact same options the\n> > user would supply to diff, and thus would have to be told all the same\n> > options, right?  I don't see how this removes any complexity at all\n> > for the user.\n> >\n> > Unless...is the request in some way similar to merge's diffstat where\n> > there is always a very specific type of diff that is wanted and you\n> > aren't envisioning much flexibility in what kind of diff or what to\n> > diff against -- is that where the simplification comes from?\n>\n> Well I just think the default diff output could be tailored for the\n> most likely usecases and options made available later for more\n> advanced usecases or users.\n\nAh, so this may go against your earlier comments at [1] about a\nmerge-tree on steroids and a huge array of diff options, or against\nyour comments about diffs not being provided by default (also [1]).\nBecause if you have that huge range of diff options, and the diffs are\nnot provided by default, then it's not clear how you've simplified\nthings because users would still need to figure out the right\narguments and options to pass, it's just that the user would have to\npass all (or maybe just most?) of those arguments and options to\nmerge-tree instead of to diff.  Or is the simpler UI you're discussing\nreally just about not needing to include 1 argument, the name of the\nnew toplevel tree, since that single argument could be implicit?\n\nI'm having a hard time buying the \"simpler UI for script writers\"\nangle of argument here, especially for script writers who should fully\nbe able to look up the appropriate commands and use them.  Your\nearlier arguments about performance being important (having both the\nmerge & the diff in the same process) seemed much more convincing to\nme.  But maybe I'm still just missing something about your \"simpler\"\nangle?\n\n[1] https://lore.kernel.org/git/CAP8UFD0wKnAg5oyMWchXysPTg3K9Vb4M1tRcPzPE81QM903pYg@mail.gmail.com/\n"},{"id":"447287","messageId":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.git.1642888562.gitgitgadget@gmail.com","subject":"[PATCH v2 00/13] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:00Z","receivedAt":"2022-01-29T18:07:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Note: Depends on en/remerge-diff\n\n(Note2: This is a continuation of my series at [0], but I can't change the\nbase repository for a pull request in GitHub so I had to open a new PR and\nthat makes it look like a new series. [0]\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/)\n\n== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs.\n\n== Updates Log ==\n\nStuff NOT included that reviewers brought up in the last round:\n\n * Very generic (mode, oid, stage, filename) printing formatting[1]\n * Always printing 3 stages for each filename with conflicts[2] [1]\n   https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n   [2]\n   https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n\nUpdates since v3 (which looks like v1 due to the re-submit; thanks to René,\nÆvar, Christian, Dscho for very helpful feedback):\n\n * New patch from Dscho allowing diff_warn_rename_limit() to print somewhere\n   other than stdout (I hope he's okay with me including his Signed-off-by)\n * Now prints filenames relative to prefix, much like ls-files\n * Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave\n   it a -l shorthand; I'm wondering if I should just drop this option,\n   though.\n * And numerous cleanups, in lots of areas:\n   * Multiple parse-options cleanups\n   * Lots of commit message cleanups\n   * Wording tweaks to the \"Description\" section of the manual\n   * Several small code cleanups\n * I dropped the RFC label\n\nUpdates since v2 (thanks to Christian, Dscho, Ramsay, and René for\nsuggestions and comments on v2):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since v1 (thanks to Johannes Altmanninger and Fabian for suggestions\non v1):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\nElijah Newren (12):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-ort: allow update messages to be written to different file\n    stream\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\nJohannes Schindelin (1):\n  diff: allow diff_warn_rename_limit to write somewhere besides stdout\n\n Documentation/git-merge-tree.txt | 179 ++++++++++++++++++++++++++++---\n builtin/merge-tree.c             | 162 +++++++++++++++++++++++++---\n diff.c                           |  20 ++--\n diff.h                           |   3 +-\n git.c                            |   2 +-\n merge-ort.c                      | 109 ++++++++++++-------\n merge-ort.h                      |  30 ++++++\n merge-recursive.c                |   3 +-\n t/t4301-merge-tree-write-tree.sh | 164 ++++++++++++++++++++++++++++\n 9 files changed, 603 insertions(+), 69 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\n\nbase-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n\nRange-diff vs v1:\n\n  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()\n  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function\n  3:  65fdae9ddba !  3:  63f42df21ae merge-tree: add option parsing and initial shell for real merge function\n     @@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)\n       }\n       \n      +struct merge_tree_options {\n     -+\tint real;\n     -+\tint trivial;\n     ++\tint mode;\n      +};\n      +\n      +static int real_merge(struct merge_tree_options *o,\n     @@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)\n      +\t\tNULL\n      +\t};\n      +\tstruct option mt_options[] = {\n     -+\t\tOPT_BOOL(0, \"write-tree\", &o.real,\n     -+\t\t\t N_(\"do a real merge instead of a trivial merge\")),\n     -+\t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n     -+\t\t\t N_(\"do a trivial merge only\")),\n     ++\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n     ++\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n     ++\t\t\t    'w'),\n     ++\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n     ++\t\t\t    N_(\"do a trivial merge only\"), 't'),\n      +\t\tOPT_END()\n      +\t};\n      +\n     -+\t/* Check for a request for basic help */\n     -+\tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n     -+\t\tusage_with_options(merge_tree_usage, mt_options);\n     -+\n      +\t/* Parse arguments */\n      +\targc = parse_options(argc, argv, prefix, mt_options,\n     -+\t\t\t     merge_tree_usage, 0);\n     -+\tif (o.real && o.trivial)\n     -+\t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n     -+\tif (o.real || o.trivial) {\n     -+\t\texpected_remaining_argc = (o.real ? 2 : 3);\n     ++\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n     ++\tif (o.mode) {\n     ++\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n      +\t\tif (argc != expected_remaining_argc)\n      +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n      +\t} else {\n      +\t\tif (argc < 2 || argc > 3)\n      +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n     -+\t\to.real = (argc == 2);\n     ++\t\to.mode = (argc == 2 ? 'w' : 't');\n      +\t}\n      +\n      +\t/* Do the relevant type of merge */\n     -+\tif (o.real)\n     ++\tif (o.mode == 'w')\n      +\t\treturn real_merge(&o, argv[0], argv[1]);\n      +\telse\n      +\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n  4:  05bd17686e1 !  4:  02c29f920d0 merge-tree: implement real merges\n     @@ Commit message\n      \n          The only output is:\n            - the toplevel resulting tree printed on stdout\n     -      - exit status of 0 (clean) or 1 (conflicts present)\n     +      - exit status of 0 (clean), 1 (conflicts present), anything else\n     +        (merge could not be performed; unknown if clean or conflicted)\n      \n          This output is meant to be used by some higher level script, perhaps in\n          a sequence of steps like this:\n     @@ Commit message\n          preliminary implementation, but subsequent commits will add that\n          ability.\n      \n     +    This also marks the traditional trivial merge of merge-tree as\n     +    deprecated.  The trivial merge not only had limited applicability, the\n     +    output format was also difficult to work with (and its format\n     +    undocumented), and will generally be less performant than real merges.\n     +\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n       [verse]\n      -'git merge-tree' <base-tree> <branch1> <branch2>\n      +'git merge-tree' [--write-tree] <branch1> <branch2>\n     -+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>\n     ++'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n       \n       DESCRIPTION\n       -----------\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +Performs a merge, but does not make any new commits and does not read\n      +from or write to either the working tree or index.\n      +\n     -+The first form will merge the two branches, doing a full recursive\n     -+merge with rename detection.  The rest of this manual (other than the\n     -+next paragraph) describes the first form in more detail -- including\n     -+options, output format, exit status, and usage notes.\n     -+\n     -+The second form is deprecated; it is kept for backward compatibility\n     -+reasons but may be deleted in the future.  It will only do a trivial\n     -+merge.  It reads three tree-ish, and outputs trivial merge results and\n     -+conflicting stages to the standard output in a semi-diff format.\n     -+Since this was designed for higher level scripts to consume and merge\n     -+the results back into the index, it omits entries that match\n     -+<branch1>.  The result of this second form is is similar to what\n     -+three-way 'git read-tree -m' does, but instead of storing the results\n     -+in the index, the command outputs the entries to the standard output.\n     -+This form not only has limited applicability, the output format is\n     -+also difficult to work with, and it will generally be less performant\n     -+than the first form even on successful merges (especially if working\n     -+in large repositories).  The remainder of this manual will only\n     -+discuss the first form.\n     ++The second form is deprecated and supported only for backward\n     ++compatibility.  It will likely be removed in the future, and will not\n     ++be discussed further in this manual.\n     ++\n     ++The first form will merge the two branches, doing a real merge.  A real\n     ++merge is distinguished from a trivial merge in that it includes:\n     ++\n     ++  * three way content merges of individual files\n     ++  * rename detection\n     ++  * proper directory/file conflict handling\n     ++  * recursive ancestor consolidation (i.e. when there is more than one\n     ++    merge base, creating a virtual merge base by merging the merge bases)\n     ++  * etc.\n     ++\n     ++After the merge completes, it will create a new toplevel tree object.\n     ++See `OUTPUT` below for details.\n      +\n      +OUTPUT\n      +------\n     @@ builtin/merge-tree.c: struct merge_tree_options {\n      +\n      +\tparent1 = get_merge_parent(branch1);\n      +\tif (!parent1)\n     -+\t\thelp_unknown_ref(branch1, \"merge\",\n     ++\t\thelp_unknown_ref(branch1, \"merge-tree\",\n      +\t\t\t\t _(\"not something we can merge\"));\n      +\n      +\tparent2 = get_merge_parent(branch2);\n      +\tif (!parent2)\n     -+\t\thelp_unknown_ref(branch2, \"merge\",\n     ++\t\thelp_unknown_ref(branch2, \"merge-tree\",\n      +\t\t\t\t _(\"not something we can merge\"));\n      +\n      +\tinit_merge_options(&opt, the_repository);\n     -+\t/*\n     -+\t * TODO: Support subtree and other -X options?\n     -+\tif (use_strategies_nr == 1 &&\n     -+\t    !strcmp(use_strategies[0]->name, \"subtree\"))\n     -+\t\topt.subtree_shift = \"\";\n     -+\tfor (x = 0; x < xopts_nr; x++)\n     -+\t\tif (parse_merge_opt(&opt, xopts[x]))\n     -+\t\t\tdie(_(\"Unknown strategy option: -X%s\"), xopts[x]);\n     -+\t*/\n      +\n      +\topt.show_rename_progress = 0;\n      +\n     -+\topt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */\n     -+\topt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */\n     ++\topt.branch1 = branch1;\n     ++\topt.branch2 = branch2;\n      +\n      +\t/*\n      +\t * Get the merge bases, in reverse order; see comment above\n     @@ builtin/merge-tree.c: struct merge_tree_options {\n      +\t\tcommit_list_insert(j->item, &merge_bases);\n      +\n      +\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n     -+\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n      +\tif (result.clean < 0)\n      +\t\tdie(_(\"failure to merge\"));\n     -+\telse if (!result.clean)\n     -+\t\tprintf(_(\"Conflicts!\\n\"));\n     ++\tputs(oid_to_hex(&result.tree->object.oid));\n      +\tmerge_finalize(&opt, &result);\n      +\treturn !result.clean; /* result.clean < 0 handled above */\n       }\n       \n       int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n      \n     - ## t/t4301-merge-tree-real.sh (new) ##\n     + ## t/t4301-merge-tree-write-tree.sh (new) ##\n      @@\n      +#!/bin/sh\n      +\n     @@ t/t4301-merge-tree-real.sh (new)\n      +. ./test-lib.sh\n      +\n      +# This test is ort-specific\n     -+test \"${GIT_TEST_MERGE_ALGORITHM:-ort}\" = ort || {\n     ++if test \"${GIT_TEST_MERGE_ALGORITHM}\" != \"ort\"\n     ++then\n      +\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n      +\ttest_done\n     -+}\n     ++fi\n      +\n      +test_expect_success setup '\n      +\ttest_write_lines 1 2 3 4 5 >numbers &&\n  -:  ----------- >  5:  6fb4f4580a5 diff: allow diff_warn_rename_limit to write somewhere besides stdout\n  5:  095aa266c2b !  6:  28368c03898 merge-ort: split out a separate display_update_messages() function\n     @@ Metadata\n       ## Commit message ##\n          merge-ort: split out a separate display_update_messages() function\n      \n     -    No functional changes included in this patch; it's just a preparatory\n     -    step to allow the printed messages to be handled differently by other\n     -    callers, such as in `git merge-tree --write-tree`.\n     +    This patch includes no new code; it simply moves a bunch of lines into a\n     +    new function.  As such, there are no functional changes.  This is just a\n     +    preparatory step to allow the printed messages to be handled differently\n     +    by other callers, such as in `git merge-tree --write-tree`.\n     +\n     +    (Patch best viewed with\n     +         --color-moved --color-moved-ws=allow-indentation-change\n     +     to see that it is a simple code movement.)\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op\n      +\n      +\t/* Also include needed rename limit adjustment now */\n      +\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     -+\t\t\t       opti->renames.needed_limit, 0);\n     ++\t\t\t       opti->renames.needed_limit, 0, stdout);\n      +\n      +\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n      +}\n     @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,\n      -\n      -\t\t/* Also include needed rename limit adjustment now */\n      -\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     --\t\t\t\t       opti->renames.needed_limit, 0);\n     +-\t\t\t\t       opti->renames.needed_limit, 0, stdout);\n      -\n      -\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n      -\t}\n  6:  e3ef17eb46f !  7:  593d0c00b57 merge-ort: allow update messages to be written to different file stream\n     @@ merge-ort.c: void merge_display_update_messages(struct merge_options *opt,\n       \t\tstruct strbuf *sb = olist.items[i].util;\n       \n      -\t\tprintf(\"%s\", sb->buf);\n     -+\t\tfprintf(stream, \"%s\", sb->buf);\n     ++\t\tstrbuf_write(sb, stream);\n       \t}\n       \tstring_list_clear(&olist, 0);\n       \n     + \t/* Also include needed rename limit adjustment now */\n     + \tdiff_warn_rename_limit(\"merge.renamelimit\",\n     +-\t\t\t       opti->renames.needed_limit, 0, stdout);\n     ++\t\t\t       opti->renames.needed_limit, 0, stream);\n     + \n     + \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n     + }\n      @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,\n       \t}\n       \n  7:  2f296aeeefb !  8:  d0d30e92ecd merge-tree: support including merge messages in output\n     @@ Commit message\n          When running `git merge-tree --write-tree`, we previously would only\n          return an exit status reflecting the cleanness of a merge, and print out\n          the toplevel tree of the resulting merge.  Merges also have\n     -    informational messages, (\"Auto-merging <PATH>\", \"CONFLICT (content):\n     -    ...\", \"CONFLICT (file/directory)\", etc.)  In fact, when non-content\n     -    conflicts occur (such as file/directory, modify/delete, add/add with\n     -    differing modes, rename/rename (1to2), etc.), these informational\n     -    messages are often the only notification since these conflicts are not\n     -    representable in the contents of the file.\n     +    informational messages, such as:\n     +      * \"Auto-merging <PATH>\"\n     +      * \"CONFLICT (content): ...\"\n     +      * \"CONFLICT (file/directory)\"\n     +      * etc.\n     +    In fact, when non-content conflicts occur (such as file/directory,\n     +    modify/delete, add/add with differing modes, rename/rename (1to2),\n     +    etc.), these informational messages may be the only notification the\n     +    user gets since these conflicts are not representable in the contents\n     +    of the file.\n      \n          Add a --[no-]messages option so that callers can request these messages\n          be included at the end of the output.  Include such messages by default\n     @@ Documentation/git-merge-tree.txt: git-merge-tree - Perform merge without touchin\n       [verse]\n      -'git merge-tree' [--write-tree] <branch1> <branch2>\n      +'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n     - 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>\n     + 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n       \n       DESCRIPTION\n     -@@ Documentation/git-merge-tree.txt: than the first form even on successful merges (especially if working\n     - in large repositories).  The remainder of this manual will only\n     - discuss the first form.\n     +@@ Documentation/git-merge-tree.txt: merge is distinguished from a trivial merge in that it includes:\n     + After the merge completes, it will create a new toplevel tree object.\n     + See `OUTPUT` below for details.\n       \n      +OPTIONS\n      +-------\n     @@ Documentation/git-merge-tree.txt: be used as a part of a series of steps such as\n      \n       ## builtin/merge-tree.c ##\n      @@ builtin/merge-tree.c: static int trivial_merge(const char *base,\n     + \n       struct merge_tree_options {\n     - \tint real;\n     - \tint trivial;\n     + \tint mode;\n      +\tint show_messages;\n       };\n       \n       static int real_merge(struct merge_tree_options *o,\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n     - \t\tcommit_list_insert(j->item, &merge_bases);\n     - \n       \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n     --\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n     -+\n       \tif (result.clean < 0)\n       \t\tdie(_(\"failure to merge\"));\n     --\telse if (!result.clean)\n     --\t\tprintf(_(\"Conflicts!\\n\"));\n      +\n      +\tif (o->show_messages == -1)\n      +\t\to->show_messages = !result.clean;\n      +\n     -+\tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n     + \tputs(oid_to_hex(&result.tree->object.oid));\n      +\tif (o->show_messages) {\n      +\t\tprintf(\"\\n\");\n      +\t\tmerge_display_update_messages(&opt, &result, stdout);\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\tNULL\n       \t};\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t N_(\"do a real merge instead of a trivial merge\")),\n     - \t\tOPT_BOOL(0, \"trivial-merge\", &o.trivial,\n     - \t\t\t N_(\"do a trivial merge only\")),\n     + \t\t\t    'w'),\n     + \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n     + \t\t\t    N_(\"do a trivial merge only\"), 't'),\n      +\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n      +\t\t\t N_(\"also show informational/conflict messages\")),\n       \t\tOPT_END()\n       \t};\n       \n     -@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\tusage_with_options(merge_tree_usage, mt_options);\n     - \n       \t/* Parse arguments */\n      +\toriginal_argc = argc;\n       \targc = parse_options(argc, argv, prefix, mt_options,\n     - \t\t\t     merge_tree_usage, 0);\n     - \tif (o.real && o.trivial)\n     - \t\tdie(_(\"--write-tree and --trivial-merge are incompatible\"));\n     -+\tif (!o.real && original_argc < argc)\n     -+\t\tdie(_(\"--write-tree must be specified if any other options are\"));\n     - \tif (o.real || o.trivial) {\n     - \t\texpected_remaining_argc = (o.real ? 2 : 3);\n     - \t\tif (argc != expected_remaining_argc)\n     + \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n     + \tif (o.mode) {\n     +@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     + \t\t\tusage_with_options(merge_tree_usage, mt_options);\n     + \t\to.mode = (argc == 2 ? 'w' : 't');\n     + \t}\n     ++\tif (o.mode == 't' && original_argc < argc)\n     ++\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n     + \n     + \t/* Do the relevant type of merge */\n     + \tif (o.mode == 'w')\n      \n     - ## t/t4301-merge-tree-real.sh ##\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'Barf on too many arguments' '\n     + ## t/t4301-merge-tree-write-tree.sh ##\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many arguments' '\n       \tgrep \"^usage: git merge-tree\" expect\n       '\n       \n  8:  35e0ed9271a !  9:  9c2334ae9f2 merge-ort: provide a merge_get_conflicted_files() helper function\n     @@ Commit message\n          merge-ort: provide a merge_get_conflicted_files() helper function\n      \n          After a merge, this function allows the user to extract the same\n     -    information that would be printed by `ls-files -u` -- conflicted\n     +    information that would be printed by `ls-files -u`, which means\n          files with their mode, oid, and stage.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n  9:  fcbb087fa88 ! 10:  243134dc247 merge-tree: provide a list of which files have conflicts\n     @@ builtin/merge-tree.c: struct merge_tree_options {\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\to->show_messages = !result.clean;\n       \n     - \tprintf(\"%s\\n\", oid_to_hex(&result.tree->object.oid));\n     + \tputs(oid_to_hex(&result.tree->object.oid));\n      +\tif (!result.clean) {\n      +\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n      +\t\tconst char *last = NULL;\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n       \n       \t/* Do the relevant type of merge */\n     - \tif (o.real)\n     + \tif (o.mode == 'w')\n      -\t\treturn real_merge(&o, argv[0], argv[1]);\n      +\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n       \telse\n       \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n       }\n      \n     - ## t/t4301-merge-tree-real.sh ##\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such' '\n     + ## t/t4301-merge-tree-write-tree.sh ##\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '\n       \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n       \tcat <<-EOF >expect &&\n       \tHASH\n     @@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such'\n       \n       \tAuto-merging greeting\n       \tCONFLICT (content): Merge conflict in greeting\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such' '\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '\n       \ttest_cmp expect actual\n       '\n       \n 10:  050add3e498 ! 11:  c322e4c6938 merge-tree: provide easy access to `ls-files -u` style info\n     @@ Commit message\n          Much like `git merge` updates the index with information of the form\n              (mode, oid, stage, name)\n          provide this output for conflicted files for merge-tree as well.\n     -    Provide an --exclude-oids-and-modes option for users to exclude the\n     -    mode, oid, and stage and only get the list of conflicted filenames.\n     +    Provide an --exclude-modes-oids-stages/-l option for users to exclude\n     +    the mode, oid, and stage and only get the list of conflicted filenames.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: discuss the first form.\n     +@@ Documentation/git-merge-tree.txt: See `OUTPUT` below for details.\n       OPTIONS\n       -------\n       \n     @@ Documentation/git-merge-tree.txt: plumbing commands since the possibility of mer\n       Part of the linkgit:git[1] suite\n      \n       ## builtin/merge-tree.c ##\n     -@@ builtin/merge-tree.c: struct merge_tree_options {\n     - \tint real;\n     - \tint trivial;\n     +@@ builtin/merge-tree.c: static int trivial_merge(const char *base,\n     + struct merge_tree_options {\n     + \tint mode;\n       \tint show_messages;\n     -+\tint exclude_oids_and_modes;\n     ++\tint exclude_modes_oids_stages;\n       };\n       \n       static int real_merge(struct merge_tree_options *o,\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\t\tconst char *name = conflicted_files.items[i].string;\n      -\t\t\tif (last && !strcmp(last, name))\n      +\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n     -+\t\t\tif (!o->exclude_oids_and_modes)\n     ++\t\t\tif (!o->exclude_modes_oids_stages)\n      +\t\t\t\tprintf(\"%06o %s %d\\t\",\n      +\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n      +\t\t\telse if (last && !strcmp(last, name))\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\t\twrite_name_quoted_relative(\n       \t\t\t\tname, prefix, stdout, line_termination);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t N_(\"do a trivial merge only\")),\n     + \t\t\t    N_(\"do a trivial merge only\"), 't'),\n       \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n       \t\t\t N_(\"also show informational/conflict messages\")),\n     -+\t\tOPT_BOOL_F(0, \"exclude-oids-and-modes\",\n     -+\t\t\t   &o.exclude_oids_and_modes,\n     -+\t\t\t   N_(\"list conflicted files without oids and modes\"),\n     ++\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n     ++\t\t\t   &o.exclude_modes_oids_stages,\n     ++\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n      +\t\t\t   PARSE_OPT_NONEG),\n       \t\tOPT_END()\n       \t};\n       \n      \n     - ## t/t4301-merge-tree-real.sh ##\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'Content merge and a few conflicts' '\n     + ## t/t4301-merge-tree-write-tree.sh ##\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Content merge and a few conflicts' '\n       \texpected_tree=$(cat .git/AUTO_MERGE) &&\n       \n       \t# We will redo the merge, while we are still in a conflicted state!\n     @@ t/t4301-merge-tree-real.sh: test_expect_success 'Content merge and a few conflic\n       \ttest_when_finished \"git reset --hard\" &&\n       \n       \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'Barf on too many arguments' '\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many arguments' '\n       '\n       \n       test_expect_success 'test conflict notices and such' '\n      -\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n     -+\ttest_expect_code 1 git merge-tree --write-tree --exclude-oids-and-modes side1 side2 >out &&\n     ++\ttest_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n       \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n       \n       \t# Expected results:\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such' '\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '\n       '\n       \n       test_expect_success 'Just the conflicted files without the messages' '\n      -\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n     -+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-oids-and-modes side1 side2 >out &&\n     ++\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&\n       \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n       \n       \ttest_write_lines HASH greeting whatever~side1 >expect &&\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'Just the conflicted files without the messages' '\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Just the conflicted files without the messages' '\n       \ttest_cmp expect actual\n       '\n       \n 11:  ba8a50f03cb ! 12:  25677d5038c merge-tree: add a --allow-unrelated-histories flag\n     @@ Documentation/git-merge-tree.txt: OPTIONS\n      \n       ## builtin/merge-tree.c ##\n      @@ builtin/merge-tree.c: static int trivial_merge(const char *base,\n     + \n       struct merge_tree_options {\n     - \tint real;\n     - \tint trivial;\n     + \tint mode;\n      +\tint allow_unrelated_histories;\n       \tint show_messages;\n     - \tint exclude_oids_and_modes;\n     + \tint exclude_modes_oids_stages;\n       };\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t * merge_incore_recursive in merge-ort.h\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \tfor (j = common; j; j = j->next)\n       \t\tcommit_list_insert(j->item, &merge_bases);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t   &o.exclude_oids_and_modes,\n     - \t\t\t   N_(\"list conflicted files without oids and modes\"),\n     + \t\t\t   &o.exclude_modes_oids_stages,\n     + \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n       \t\t\t   PARSE_OPT_NONEG),\n      +\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n      +\t\t\t   &o.allow_unrelated_histories,\n     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char\n       \t};\n       \n      \n     - ## t/t4301-merge-tree-real.sh ##\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success setup '\n     + ## t/t4301-merge-tree-write-tree.sh ##\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success setup '\n       \t>whatever/empty &&\n       \tgit add numbers greeting whatever/empty &&\n       \ttest_tick &&\n     @@ t/t4301-merge-tree-real.sh: test_expect_success setup '\n       '\n       \n       test_expect_success 'Content merge and a few conflicts' '\n     -@@ t/t4301-merge-tree-real.sh: test_expect_success 'Check conflicted oids and modes without messages' '\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and modes without messages' '\n       \ttest_cmp conflicted-file-info actual\n       '\n       \n 12:  4123209cafc = 13:  e7c63425a0e git-merge-tree.txt: add a section on potentional usage mistakes\n\n-- \ngitgitgadget\n"},{"id":"447286","messageId":"4a7cd5542bb2f89b4874e4115542ccee9c4639af.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 01/13] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:01Z","receivedAt":"2022-01-29T18:07:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"447288","messageId":"4780ff6784d426bf0a96859ef9bf9c14e87d5f50.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 02/13] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:02Z","receivedAt":"2022-01-29T18:07:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"447289","messageId":"63f42df21aec5bda50e4414493eb59dcb64e5558.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 03/13] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:03Z","receivedAt":"2022-01-29T18:07:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 61 +++++++++++++++++++++++++++++++++++++-------\n git.c                |  2 +-\n 2 files changed, 53 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..e98ec8a9f1d 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,51 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+struct merge_tree_options {\n+\tint mode;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n+\t\t\t    'w'),\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n+\t\t\t    N_(\"do a trivial merge only\"), 't'),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (o.mode) {\n+\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n+\t\tif (argc != expected_remaining_argc)\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t} else {\n+\t\tif (argc < 2 || argc > 3)\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\to.mode = (argc == 2 ? 'w' : 't');\n+\t}\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.mode == 'w')\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..6090a1289db 100644\n--- a/git.c\n+++ b/git.c\n@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"447290","messageId":"02c29f920d0d5fde6d85f7b86a69be92e3f0f34d.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 04/13] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:04Z","receivedAt":"2022-01-29T18:07:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean), 1 (conflicts present), anything else\n    (merge could not be performed; unknown if clean or conflicted)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability.\n\nThis also marks the traditional trivial merge of merge-tree as\ndeprecated.  The trivial merge not only had limited applicability, the\noutput format was also difficult to work with (and its format\nundocumented), and will generally be less performant than real merges.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 71 +++++++++++++++++++++-----\n builtin/merge-tree.c             | 44 +++++++++++++++-\n t/t4301-merge-tree-write-tree.sh | 88 ++++++++++++++++++++++++++++++++\n 3 files changed, 190 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..569485815a0 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,73 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The second form is deprecated and supported only for backward\n+compatibility.  It will likely be removed in the future, and will not\n+be discussed further in this manual.\n+\n+The first form will merge the two branches, doing a real merge.  A real\n+merge is distinguished from a trivial merge in that it includes:\n+\n+  * three way content merges of individual files\n+  * rename detection\n+  * proper directory/file conflict handling\n+  * recursive ancestor consolidation (i.e. when there is more than one\n+    merge base, creating a virtual merge base by merging the merge bases)\n+  * etc.\n+\n+After the merge completes, it will create a new toplevel tree object.\n+See `OUTPUT` below for details.\n+\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1.\n+\n+USAGE NOTES\n+-----------\n+\n+git-merge-tree was written to be low-level plumbing, similar to\n+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n+be used as a part of a series of steps such as\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n+\n+However, it does not quite fit into the same category of low-level\n+plumbing commands since the possibility of merge conflicts give it a\n+much higher chance of the command not succeeding.\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex e98ec8a9f1d..d14c9f6e44e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -392,7 +395,46 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *common;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct commit_list *j;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = branch1;\n+\topt.branch2 = branch2;\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tcommon = get_merge_bases(parent1, parent2);\n+\tif (!common)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\tputs(oid_to_hex(&result.tree->object.oid));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nnew file mode 100755\nindex 00000000000..66c3eaf2021\n--- /dev/null\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -0,0 +1,88 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+if test \"${GIT_TEST_MERGE_ALGORITHM}\" != \"ort\"\n+then\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+fi\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(cat .git/AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"447291","messageId":"6fb4f4580a581b2e43bc4b8deaa3d2d2bf4a8756.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 05/13] diff: allow diff_warn_rename_limit to write somewhere besides stdout","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:05Z","receivedAt":"2022-01-29T18:07:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\ndiff_warn_rename_limit() is hardcoded to write to stdout.  Make it\naccept an output location parameter to make it more flexible.\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n diff.c            | 20 ++++++++++++++------\n diff.h            |  3 ++-\n merge-ort.c       |  2 +-\n merge-recursive.c |  3 ++-\n 4 files changed, 19 insertions(+), 9 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 1bfb01c18ec..6952035046f 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6377,17 +6377,25 @@ static const char rename_limit_advice[] =\n N_(\"you may want to set your %s variable to at least \"\n    \"%d and retry the command.\");\n \n-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)\n+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n+\t\t\t    FILE *out)\n {\n-\tfflush(stdout);\n+\tconst char *fmt = NULL;\n+\n \tif (degraded_cc)\n-\t\twarning(_(degrade_cc_to_c_warning));\n+\t\tfmt = _(degrade_cc_to_c_warning);\n \telse if (needed)\n-\t\twarning(_(rename_limit_warning));\n+\t\tfmt = _(rename_limit_warning);\n \telse\n \t\treturn;\n \tif (0 < needed)\n-\t\twarning(_(rename_limit_advice), varname, needed);\n+\t\tfmt = _(rename_limit_advice);\n+\n+\tfflush(out);\n+\tif (out == stdout)\n+\t\twarning(fmt, varname, needed);\n+\telse\n+\t\tfprintf(out, fmt, varname, needed);\n }\n \n static void create_filepairs_for_header_only_notifications(struct diff_options *o)\n@@ -6870,7 +6878,7 @@ int diff_result_code(struct diff_options *opt, int status)\n \n \tdiff_warn_rename_limit(\"diff.renameLimit\",\n \t\t\t       opt->needed_rename_limit,\n-\t\t\t       opt->degraded_cc_to_c);\n+\t\t\t       opt->degraded_cc_to_c, stdout);\n \tif (!opt->flags.exit_with_status &&\n \t    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))\n \t\treturn status;\ndiff --git a/diff.h b/diff.h\nindex ce9e2cf2e4f..40c5b78fb0a 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -597,7 +597,8 @@ void diffcore_fix_diff_index(void);\n int diff_queue_is_empty(struct diff_options *o);\n void diff_flush(struct diff_options*);\n void diff_free(struct diff_options*);\n-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);\n+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n+\t\t\t    FILE *out);\n \n /* diff-raw status letters */\n #define DIFF_STATUS_ADDED\t\t'A'\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9bf15a01db8..65618048b59 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4305,7 +4305,7 @@ void merge_switch_to_result(struct merge_options *opt,\n \n \t\t/* Also include needed rename limit adjustment now */\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n+\t\t\t\t       opti->renames.needed_limit, 0, stdout);\n \n \t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 9ec1e6d043a..d2eeca9fa20 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3738,7 +3738,8 @@ static void merge_finalize(struct merge_options *opt)\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->priv->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0,\n+\t\t\t\t       stdout);\n \tFREE_AND_NULL(opt->priv);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"447292","messageId":"28368c03898047e2e5d3d3c4ad7ffef517ff2450.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 06/13] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:06Z","receivedAt":"2022-01-29T18:07:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis patch includes no new code; it simply moves a bunch of lines into a\nnew function.  As such, there are no functional changes.  This is just a\npreparatory step to allow the printed messages to be handled differently\nby other callers, such as in `git merge-tree --write-tree`.\n\n(Patch best viewed with\n     --color-moved --color-moved-ws=allow-indentation-change\n to see that it is a simple code movement.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 77 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 36 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 65618048b59..1ada3198390 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0, stdout);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4273,42 +4312,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n \n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0, stdout);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"447293","messageId":"593d0c00b574aa8097badd45d786097ec8601e18.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 07/13] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:07Z","receivedAt":"2022-01-29T18:07:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis modifies the new display_update_messages() function to allow\nprinting to somewhere other than stdout.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 9 +++++----\n merge-ort.h | 3 ++-\n 2 files changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 1ada3198390..d28d1721d14 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n }\n \n void merge_display_update_messages(struct merge_options *opt,\n-\t\t\t\t   struct merge_result *result)\n+\t\t\t\t   struct merge_result *result,\n+\t\t\t\t   FILE *stream)\n {\n \tstruct merge_options_internal *opti = result->priv;\n \tstruct hashmap_iter iter;\n@@ -4263,13 +4264,13 @@ void merge_display_update_messages(struct merge_options *opt,\n \tfor (i = 0; i < olist.nr; ++i) {\n \t\tstruct strbuf *sb = olist.items[i].util;\n \n-\t\tprintf(\"%s\", sb->buf);\n+\t\tstrbuf_write(sb, stream);\n \t}\n \tstring_list_clear(&olist, 0);\n \n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t       opti->renames.needed_limit, 0, stdout);\n+\t\t\t       opti->renames.needed_limit, 0, stream);\n \n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n@@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,\n \t}\n \n \tif (display_update_msgs)\n-\t\tmerge_display_update_messages(opt, result);\n+\t\tmerge_display_update_messages(opt, result, stdout);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..d643b47cb7c 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,\n  * so only call this when bypassing merge_switch_to_result().\n  */\n void merge_display_update_messages(struct merge_options *opt,\n-\t\t\t\t   struct merge_result *result);\n+\t\t\t\t   struct merge_result *result,\n+\t\t\t\t   FILE *stream);\n \n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n-- \ngitgitgadget\n\n"},{"id":"447294","messageId":"c322e4c6938b7270b6e90998994642074a2813e0.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:11Z","receivedAt":"2022-01-29T18:07:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide an --exclude-modes-oids-stages/-l option for users to exclude\nthe mode, oid, and stage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 30 ++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++++--\n 3 files changed, 58 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 160e8f44b62..55bb7bc61c1 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -38,6 +38,11 @@ See `OUTPUT` below for details.\n OPTIONS\n -------\n \n+--exclude-oids-and-modes::\n+\tInstead of writing a list of (mode, oid, stage, path) tuples\n+\tto output for conflicted files, just provide a list of\n+\tfilenames with conflicts.\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -55,7 +60,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -67,18 +72,23 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--exclude-oids-and-modes` option is passed, the mode, object, and\n+stage will be omitted.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -110,6 +120,14 @@ plumbing commands since the possibility of merge conflicts give it a\n much higher chance of the command not succeeding (and NEWTREE containing\n a bunch of stuff other than just a toplevel tree).\n \n+git-merge-tree was written to provide users with the same information\n+that they'd have access to if using `git merge`:\n+  * what would be written to the working tree (the <OID of toplevel tree>)\n+  * the higher order stages that would be written to the index (the\n+    <Conflicted file info>)\n+  * any messages that would have been printed to stdout (the <Informational\n+    messages>)\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 54dae018203..dc52cd02dce 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -394,6 +394,7 @@ static int trivial_merge(const char *base,\n struct merge_tree_options {\n \tint mode;\n \tint show_messages;\n+\tint exclude_modes_oids_stages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->exclude_modes_oids_stages)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), 't'),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n+\t\t\t   &o.exclude_modes_oids_stages,\n+\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 7113d060bc5..1572f460da0 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -47,6 +47,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(cat .git/AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -86,7 +87,7 @@ test_expect_success 'Barf on too many arguments' '\n '\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \t# Expected results:\n@@ -109,7 +110,7 @@ test_expect_success 'test conflict notices and such' '\n '\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -117,4 +118,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447295","messageId":"243134dc2478e21f67a6d9cb999d6754b616f6ee.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 10/13] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:10Z","receivedAt":"2022-01-29T18:07:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  8 ++++++++\n builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 11 +++++++++++\n 3 files changed, 41 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 42e0f8f6183..160e8f44b62 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -55,6 +55,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -66,6 +67,13 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 6a556ab1c9c..54dae018203 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -394,7 +397,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *common;\n@@ -438,6 +442,22 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n \t\tprintf(\"\\n\");\n \t\tmerge_display_update_messages(&opt, &result, stdout);\n@@ -486,7 +506,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == 'w')\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex e2255711f9c..7113d060bc5 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -95,6 +95,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -106,4 +108,13 @@ test_expect_success 'test conflict notices and such' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447296","messageId":"9c2334ae9f294b92c0c34b3d13efc2b16eefecb4.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 09/13] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:09Z","receivedAt":"2022-01-29T18:07:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u`, which means\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex d28d1721d14..c4ce6027dc4 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex d643b47cb7c..e635a294ea8 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result,\n \t\t\t\t   FILE *stream);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"447297","messageId":"d0d30e92ecd9dff6174a39a94a9e7d7e29896fd4.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 08/13] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:08Z","receivedAt":"2022-01-29T18:07:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, such as:\n  * \"Auto-merging <PATH>\"\n  * \"CONFLICT (content): ...\"\n  * \"CONFLICT (file/directory)\"\n  * etc.\nIn fact, when non-content conflicts occur (such as file/directory,\nmodify/delete, add/add with differing modes, rename/rename (1to2),\netc.), these informational messages may be the only notification the\nuser gets since these conflicts are not representable in the contents\nof the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----\n builtin/merge-tree.c             | 19 ++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 21 +++++++++++++++\n 3 files changed, 76 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 569485815a0..42e0f8f6183 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n@@ -35,17 +35,47 @@ merge is distinguished from a trivial merge in that it includes:\n After the merge completes, it will create a new toplevel tree object.\n See `OUTPUT` below for details.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+By default, for a successful merge, the output from git-merge-tree is\n+simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n+\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -69,7 +99,8 @@ be used as a part of a series of steps such as\n \n However, it does not quite fit into the same category of low-level\n plumbing commands since the possibility of merge conflicts give it a\n-much higher chance of the command not succeeding.\n+much higher chance of the command not succeeding (and NEWTREE containing\n+a bunch of stuff other than just a toplevel tree).\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex d14c9f6e44e..6a556ab1c9c 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -390,6 +390,7 @@ static int trivial_merge(const char *base,\n \n struct merge_tree_options {\n \tint mode;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -432,18 +433,27 @@ static int real_merge(struct merge_tree_options *o,\n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result, stdout);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -453,10 +463,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    'w'),\n \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n \t\t\t    N_(\"do a trivial merge only\"), 't'),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n \t/* Parse arguments */\n+\toriginal_argc = argc;\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n \tif (o.mode) {\n@@ -468,6 +481,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(merge_tree_usage, mt_options);\n \t\to.mode = (argc == 2 ? 'w' : 't');\n \t}\n+\tif (o.mode == 't' && original_argc < argc)\n+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == 'w')\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 66c3eaf2021..e2255711f9c 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -85,4 +85,25 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447298","messageId":"e7c63425a0e4070a3bfd0e7614d7447a42e3d598.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 13/13] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:13Z","receivedAt":"2022-01-29T18:07:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex d35710c81d5..b97ddc58a7a 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -133,6 +133,52 @@ that they'd have access to if using `git merge`:\n   * any messages that would have been printed to stdout (the <Informational\n     messages>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <Conflicted file info> section instead.  Not\n+only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n+check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <Conflicted file info> list.  The information there is insufficient\n+to do so.  For example: Rename/rename(1to2) conflicts (both sides\n+renamed the same file differently) will result in three different file\n+having higher order stages (but each only has one higher order stage),\n+with no way (short of the <Informational messages> section) to determine\n+which three files are related.  File/directory conflicts also result in\n+a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<Informational messages> section has the necessary info, though it is\n+not designed to be machine parseable.\n+\n+Do NOT assume all filenames listed in the <Informational messages>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <Conflicted file info> and re-merging\n+them to present the conflicts to the user.  This will lose information.\n+Instead, look up the version of the file found within the <OID of\n+toplevel tree> and show that instead.  In particular, the latter will\n+have conflict markers annotated with the original branch/commit being\n+merged and, if renames were involved, the original filename.  While you\n+could include the original branch/commit in the conflict marker\n+annotations when re-merging, the original filename is not available from\n+the <Conflicted file info> and thus you would be losing information that\n+might help the user resolve the conflict.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n"},{"id":"447300","messageId":"25677d5038cab591774eaa1349365871b23aa2fc.1643479633.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v2 12/13] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-29T18:07:12Z","receivedAt":"2022-01-29T18:09:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 55bb7bc61c1..d35710c81d5 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -49,6 +49,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n OUTPUT\n ------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex dc52cd02dce..cca5075d521 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -393,6 +393,7 @@ static int trivial_merge(const char *base,\n \n struct merge_tree_options {\n \tint mode;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint exclude_modes_oids_stages;\n };\n@@ -430,7 +431,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common)\n+\tif (!common && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tfor (j = common; j; j = j->next)\n \t\tcommit_list_insert(j->item, &merge_bases);\n@@ -494,6 +495,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.exclude_modes_oids_stages,\n \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 1572f460da0..996bdfaab7d 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -38,7 +38,13 @@ test_expect_success setup '\n \t>whatever/empty &&\n \tgit add numbers greeting whatever/empty &&\n \ttest_tick &&\n-\tgit commit -m other-modifications\n+\tgit commit -m other-modifications &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Content merge and a few conflicts' '\n@@ -139,4 +145,20 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447349","messageId":"CABPp-BFovPdTa6yXTbu2QvnD6a+DV5LAZ+CPNEcKD+5=dgsYfg@mail.gmail.com","threadId":"57288","inReplyTo":"CABPp-BGYAgUbfJVMXTOTq3mMcBsFvrvRKA6KpLkcdDj7NvFEhA@mail.gmail.com","subject":"Re: [PATCH 00/12] RFC: In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-01-31T17:45:10Z","receivedAt":"2022-01-31T17:45:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sat, Jan 29, 2022 at 9:43 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> Hi Christian,\n>\n> On Sat, Jan 29, 2022 at 12:18 AM Christian Couder\n> <christian.couder@gmail.com> wrote:\n> >\n> > On Sat, Jan 29, 2022 at 8:04 AM Elijah Newren <newren@gmail.com> wrote:\n> > >\n> > > On Wed, Jan 26, 2022 at 12:48 AM Christian Couder\n> > > <christian.couder@gmail.com> wrote:\n[...]\n> > > > The reason is\n> > > > that I think in many cases when there are conflicts, the conflicts\n> > > > will be small and the user will want to see them.\n> > >\n> > > I'm a little worried about the assumption here that conflict size is\n> > > measurable and visible via diffs.  That might be true in some cases,\n> > > but a UI written with that assumption is going to be very confusing\n> > > when hitting cases where that assumption does not hold.  For example:\n> > >\n> > >   * What if there is a binary file conflict, or a modify/delete or\n> > > rename/delete conflict, or failed-to-merge submodule conflict, or a\n> > > file location conflict? (For these, there is no diff relative to the\n> > > first parent and hence this conflict would have no diff output for\n> > > it)?\n> > >   * What if there was a simple file/directory conflict?  A diff would\n> > > show a rename (even when neither side did any renames), but not any\n> > > conflict markers.\n> > >   * What if there was a rename/rename conflict (both sides renamed\n> > > same file differently) or a distinct types conflict?  The former\n> > > results in three different conflicting files, none of them with\n> > > conflict markers, while the latter results in two different\n> > > conflicting files both without conflict markers?  Showing individual\n> > > per-file diffs totally loses all context here -- it'll show no-diff\n> > > for one of the files, and totally new additions for the ones.\n> >\n> > In those cases we just tell users that they cannot resolve those\n> > conflicts in the user interface, see the following doc:\n> >\n> > https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#conflicts-you-can-resolve-in-the-user-interface\n>\n> So...I think you may have just convinced me that my fears were\n> justified and that I should probably NAK any attempt to add diffs to\n> the merge-tree command.  I won't jump to conclusions but you've\n> provided some pretty strong signal to me against going down that\n> route.  The list of limitations in the link you provide do mostly\n> avoid the broken cases I listed above, but it enshrines those\n> limitations on that webpage as fundamental rather than just as current\n> implementation shortcomings.  You may not be able to remove those\n> limitations on that webpage without either expunging the diffs from\n> the UI or exposing the brokenness of the various cases above.\n>\n> If you do propose a diff option in the future, come prepared to\n> discuss how you'll avoid accidentally leading others down into paths\n> with the same fundamental issues, and/or how the above types of\n> conflicts might still be meaningfully handled.\n\nActually, after having a few extra days to think about it, I thought\nof something that should have been obvious to me, given my other\nin-flight series that this depends upon...\n\nIf you used the same trick that remerge-diff does to include the\nCONFLICT (and related messages) headers in the diff, then the kinds of\nconflicts that are normally either invisible or misleading/confusing\nto show via a diff would suddenly have the extra notices needed to\nexplain them, and make this problem tractable.\n\nFurther, it'd only make sense to do the special diff as part of the\nmerge-tree process, since it has the conflict messages strmap needed\nto do this.\n\nAnd there's not all that much work that would be needed to take\nadvantage of this, especially since this series already depends upon\nthe remerge-diff series.\n\nSo, maybe this is fine after all.\n\n> Also, the list of limitations you have may not be quite comprehensive\n> enough to avoid all problems (though it certainly avoids most of\n> them).  Can I ask a couple clarifying questions about your list of\n> limitations in that link? :\n>\n>   * When that page says the file cannot already contain conflict\n> markers, is the check performed on the version of the file in the two\n> trees being merged, or is the check performed on the 2nd and 3rd index\n> stage of the merge result (these are not equivalent checks, even if\n> they often give the same answer)?\n>   * When that page says the file must already exist in the same path\n> on both branches, is the check performed on by checking the path in\n> the two trees being merged, or is the check performed on the 2nd and\n> 3rd index stage of the merge result (again, these are not equivalent\n> checks)?\n\nI am still curious about this either way.\n"},{"id":"447507","messageId":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v2.git.1643479633.gitgitgadget@gmail.com","subject":"[PATCH v3 00/15] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:26Z","receivedAt":"2022-02-02T07:34:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Note: Depends on en/remerge-diff (to avoid a small textual conflict). Still\nunder heavy discussion, though, so no need to pick up yet.\n\n== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs.\n\n== Quick Overview ==\n\n * Patches 1-2: preparatory cleanups\n * Patches 3-4: implement basic real merges\n * Patches 5-9: include informational messages (\"CONFLICT\" messages and\n   such) in output\n   * to be honest, patches 5, 6, & 8 may be less relevant since we're now\n     including these messages on stdout anyway...\n * Patches 10-13: add ability to include ls-files -u style of info in the\n   output\n * Patch 14: support --allow-unrelated-histories\n * Patch 15: augment the manual with potential usage mistakes\n\n== Updates Log ==\n\nUpdates since v2 (or v4, if you include the rounds at\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/):\n\n * Improved patches from Dscho for the diff_warn_rename_limit() handling\n * Add a -z option for NUL-terminated conflict info lines (so that filenames\n   do not have to be quoted)\n\nStuff NOT included that reviewers brought up in earlier rounds:\n\n * Very generic (mode, oid, stage, filename) printing formatting[1]\n * Always printing 3 stages for each filename with conflicts[2] [1]\n   https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n   [2]\n   https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n\nUpdates since v1 (or v3 depending on how you count; thanks to René, Ævar,\nChristian, Dscho for very helpful feedback):\n\n * New patch from Dscho allowing diff_warn_rename_limit() to print somewhere\n   other than stdout (I hope he's okay with me including his Signed-off-by)\n * Now prints filenames relative to prefix, much like ls-files\n * Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave\n   it a -l shorthand; I'm wondering if I should just drop this option,\n   though.\n * And numerous cleanups, in lots of areas:\n   * Multiple parse-options cleanups\n   * Lots of commit message cleanups\n   * Wording tweaks to the \"Description\" section of the manual\n   * Several small code cleanups\n * I dropped the RFC label\n\nUpdates since original submission v2 (thanks to Christian, Dscho, Ramsay,\nand René for suggestions and comments):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since original submission v1 (thanks to Johannes Altmanninger and\nFabian for suggestions):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\nElijah Newren (13):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-ort: allow update messages to be written to different file\n    stream\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-tree: allow `ls-files -u` style info to be NUL terminated\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\nJohannes Schindelin (2):\n  Introduce a variant of the `warning()` function that takes a `FILE *`\n  diff: allow diff_warn_rename_limit to write somewhere besides stderr\n\n Documentation/git-merge-tree.txt | 192 +++++++++++++++++++++++++++--\n builtin/merge-tree.c             | 164 +++++++++++++++++++++++--\n diff.c                           |  13 +-\n diff.h                           |   3 +-\n git-compat-util.h                |   1 +\n git.c                            |   2 +-\n merge-ort.c                      | 109 +++++++++++------\n merge-ort.h                      |  30 +++++\n merge-recursive.c                |   3 +-\n t/t4301-merge-tree-write-tree.sh | 204 +++++++++++++++++++++++++++++++\n usage.c                          |  14 +++\n 11 files changed, 667 insertions(+), 68 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\n\nbase-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n\nRange-diff vs v2:\n\n  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()\n  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function\n  3:  63f42df21ae =  3:  63f42df21ae merge-tree: add option parsing and initial shell for real merge function\n  4:  02c29f920d0 =  4:  02c29f920d0 merge-tree: implement real merges\n  -:  ----------- >  5:  290b42846b5 Introduce a variant of the `warning()` function that takes a `FILE *`\n  5:  6fb4f4580a5 !  6:  2083fbe9b2e diff: allow diff_warn_rename_limit to write somewhere besides stdout\n     @@ Metadata\n      Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n      \n       ## Commit message ##\n     -    diff: allow diff_warn_rename_limit to write somewhere besides stdout\n     +    diff: allow diff_warn_rename_limit to write somewhere besides stderr\n      \n     -    diff_warn_rename_limit() is hardcoded to write to stdout.  Make it\n     -    accept an output location parameter to make it more flexible.\n     +    diff_warn_rename_limit() is hardcoded to write to stderr.  Make it\n     +    accept a file stream parameter to make it more flexible.\n      \n          Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n     @@ diff.c: static const char rename_limit_advice[] =\n      +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n      +\t\t\t    FILE *out)\n       {\n     --\tfflush(stdout);\n     -+\tconst char *fmt = NULL;\n     -+\n     + \tfflush(stdout);\n       \tif (degraded_cc)\n      -\t\twarning(_(degrade_cc_to_c_warning));\n     -+\t\tfmt = _(degrade_cc_to_c_warning);\n     ++\t\twarning_fp(out, _(degrade_cc_to_c_warning));\n       \telse if (needed)\n      -\t\twarning(_(rename_limit_warning));\n     -+\t\tfmt = _(rename_limit_warning);\n     ++\t\twarning_fp(out, _(rename_limit_warning));\n       \telse\n       \t\treturn;\n     ++\n     ++\n       \tif (0 < needed)\n      -\t\twarning(_(rename_limit_advice), varname, needed);\n     -+\t\tfmt = _(rename_limit_advice);\n     -+\n     -+\tfflush(out);\n     -+\tif (out == stdout)\n     -+\t\twarning(fmt, varname, needed);\n     -+\telse\n     -+\t\tfprintf(out, fmt, varname, needed);\n     ++\t\twarning_fp(out, _(rename_limit_advice), varname, needed);\n       }\n       \n       static void create_filepairs_for_header_only_notifications(struct diff_options *o)\n     @@ diff.c: int diff_result_code(struct diff_options *opt, int status)\n       \tdiff_warn_rename_limit(\"diff.renameLimit\",\n       \t\t\t       opt->needed_rename_limit,\n      -\t\t\t       opt->degraded_cc_to_c);\n     -+\t\t\t       opt->degraded_cc_to_c, stdout);\n     ++\t\t\t       opt->degraded_cc_to_c, stderr);\n       \tif (!opt->flags.exit_with_status &&\n       \t    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))\n       \t\treturn status;\n     @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,\n       \t\t/* Also include needed rename limit adjustment now */\n       \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n      -\t\t\t\t       opti->renames.needed_limit, 0);\n     -+\t\t\t\t       opti->renames.needed_limit, 0, stdout);\n     ++\t\t\t\t       opti->renames.needed_limit, 0, stderr);\n       \n       \t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n       \t}\n     @@ merge-recursive.c: static void merge_finalize(struct merge_options *opt)\n       \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n      -\t\t\t\t       opt->priv->needed_rename_limit, 0);\n      +\t\t\t\t       opt->priv->needed_rename_limit, 0,\n     -+\t\t\t\t       stdout);\n     ++\t\t\t\t       stderr);\n       \tFREE_AND_NULL(opt->priv);\n       }\n       \n  6:  28368c03898 !  7:  1be858e6aa6 merge-ort: split out a separate display_update_messages() function\n     @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op\n      +\n      +\t/* Also include needed rename limit adjustment now */\n      +\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     -+\t\t\t       opti->renames.needed_limit, 0, stdout);\n     ++\t\t\t       opti->renames.needed_limit, 0, stderr);\n      +\n      +\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n      +}\n     @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,\n      -\n      -\t\t/* Also include needed rename limit adjustment now */\n      -\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     --\t\t\t\t       opti->renames.needed_limit, 0, stdout);\n     +-\t\t\t\t       opti->renames.needed_limit, 0, stderr);\n      -\n      -\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n      -\t}\n  7:  593d0c00b57 !  8:  04c3bdc44d2 merge-ort: allow update messages to be written to different file stream\n     @@ Commit message\n          merge-ort: allow update messages to be written to different file stream\n      \n          This modifies the new display_update_messages() function to allow\n     -    printing to somewhere other than stdout.\n     +    printing to somewhere other than stdout.  It also consolidates the\n     +    location of the diff_warn_rename_limit() message with the rest of the\n     +    CONFLICT and other update messages to all go to the same stream.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ merge-ort.c: void merge_display_update_messages(struct merge_options *opt,\n       \n       \t/* Also include needed rename limit adjustment now */\n       \tdiff_warn_rename_limit(\"merge.renamelimit\",\n     --\t\t\t       opti->renames.needed_limit, 0, stdout);\n     +-\t\t\t       opti->renames.needed_limit, 0, stderr);\n      +\t\t\t       opti->renames.needed_limit, 0, stream);\n       \n       \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n  8:  d0d30e92ecd =  9:  c8ed002408d merge-tree: support including merge messages in output\n  9:  9c2334ae9f2 = 10:  1c2a3f5ef63 merge-ort: provide a merge_get_conflicted_files() helper function\n 10:  243134dc247 = 11:  9c2389eef0e merge-tree: provide a list of which files have conflicts\n 11:  c322e4c6938 = 12:  2188a8ca1e7 merge-tree: provide easy access to `ls-files -u` style info\n  -:  ----------- > 13:  52339b396fa merge-tree: allow `ls-files -u` style info to be NUL terminated\n 12:  25677d5038c ! 14:  c854ecb5f4a merge-tree: add a --allow-unrelated-histories flag\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success setup '\n       '\n       \n       test_expect_success 'Content merge and a few conflicts' '\n     -@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and modes without messages' '\n     - \ttest_cmp conflicted-file-info actual\n     +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'NUL terminated conflicted file \"lines\"' '\n     + \ttest_cmp expect actual\n       '\n       \n      +test_expect_success 'error out by default for unrelated histories' '\n 13:  e7c63425a0e = 15:  bc8591bbb63 git-merge-tree.txt: add a section on potentional usage mistakes\n\n-- \ngitgitgadget\n"},{"id":"447508","messageId":"4a7cd5542bb2f89b4874e4115542ccee9c4639af.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 01/15] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:27Z","receivedAt":"2022-02-02T07:34:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"447509","messageId":"4780ff6784d426bf0a96859ef9bf9c14e87d5f50.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 02/15] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:28Z","receivedAt":"2022-02-02T07:34:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"447510","messageId":"63f42df21aec5bda50e4414493eb59dcb64e5558.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:29Z","receivedAt":"2022-02-02T07:34:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 61 +++++++++++++++++++++++++++++++++++++-------\n git.c                |  2 +-\n 2 files changed, 53 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..e98ec8a9f1d 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,51 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+struct merge_tree_options {\n+\tint mode;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n+\t\t\t    'w'),\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n+\t\t\t    N_(\"do a trivial merge only\"), 't'),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (o.mode) {\n+\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n+\t\tif (argc != expected_remaining_argc)\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t} else {\n+\t\tif (argc < 2 || argc > 3)\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\to.mode = (argc == 2 ? 'w' : 't');\n+\t}\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.mode == 'w')\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..6090a1289db 100644\n--- a/git.c\n+++ b/git.c\n@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"447511","messageId":"02c29f920d0d5fde6d85f7b86a69be92e3f0f34d.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:30Z","receivedAt":"2022-02-02T07:34:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean), 1 (conflicts present), anything else\n    (merge could not be performed; unknown if clean or conflicted)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability.\n\nThis also marks the traditional trivial merge of merge-tree as\ndeprecated.  The trivial merge not only had limited applicability, the\noutput format was also difficult to work with (and its format\nundocumented), and will generally be less performant than real merges.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 71 +++++++++++++++++++++-----\n builtin/merge-tree.c             | 44 +++++++++++++++-\n t/t4301-merge-tree-write-tree.sh | 88 ++++++++++++++++++++++++++++++++\n 3 files changed, 190 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..569485815a0 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,73 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The second form is deprecated and supported only for backward\n+compatibility.  It will likely be removed in the future, and will not\n+be discussed further in this manual.\n+\n+The first form will merge the two branches, doing a real merge.  A real\n+merge is distinguished from a trivial merge in that it includes:\n+\n+  * three way content merges of individual files\n+  * rename detection\n+  * proper directory/file conflict handling\n+  * recursive ancestor consolidation (i.e. when there is more than one\n+    merge base, creating a virtual merge base by merging the merge bases)\n+  * etc.\n+\n+After the merge completes, it will create a new toplevel tree object.\n+See `OUTPUT` below for details.\n+\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1.\n+\n+USAGE NOTES\n+-----------\n+\n+git-merge-tree was written to be low-level plumbing, similar to\n+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n+be used as a part of a series of steps such as\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n+\n+However, it does not quite fit into the same category of low-level\n+plumbing commands since the possibility of merge conflicts give it a\n+much higher chance of the command not succeeding.\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex e98ec8a9f1d..d14c9f6e44e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -392,7 +395,46 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *common;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct commit_list *j;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = branch1;\n+\topt.branch2 = branch2;\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tcommon = get_merge_bases(parent1, parent2);\n+\tif (!common)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\tputs(oid_to_hex(&result.tree->object.oid));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nnew file mode 100755\nindex 00000000000..66c3eaf2021\n--- /dev/null\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -0,0 +1,88 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+if test \"${GIT_TEST_MERGE_ALGORITHM}\" != \"ort\"\n+then\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+fi\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(cat .git/AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"447512","messageId":"290b42846b5557055b84e4237ddc8c3532752d5d.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 05/15] Introduce a variant of the `warning()` function that takes a `FILE *`","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:31Z","receivedAt":"2022-02-02T07:34:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWe are about to teach `diff_warn_rename_limit()` to write into a file\ninstead of `stderr`. That function wants to call `warning()` when\nwriting to `stderr`, though, allowing for the `warn_routine` to be\noverridden.\n\nLet's introduce a helper for that.\n\nNote: Since there is currently no need to provide similar functions for\n`error()` or `die()`, let alone for the `_errno` variants, we will leave\nthat to a date when the need for those should arise, if ever.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n git-compat-util.h |  1 +\n usage.c           | 14 ++++++++++++++\n 2 files changed, 15 insertions(+)\n\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex d70ce142861..64ba60e5c71 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -475,6 +475,7 @@ int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n+void warning_fp(FILE *out, const char *warn, ...) __attribute__((format (printf, 2, 3)));\n \n #ifndef NO_OPENSSL\n #ifdef APPLE_COMMON_CRYPTO\ndiff --git a/usage.c b/usage.c\nindex c7d233b0de9..0bfd2c603c0 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -253,6 +253,20 @@ void warning(const char *warn, ...)\n \tva_end(params);\n }\n \n+void warning_fp(FILE *out, const char *warn, ...)\n+{\n+\tva_list params;\n+\n+\tva_start(params, warn);\n+\tif (out == stderr)\n+\t\twarn_routine(warn, params);\n+\telse {\n+\t\tvfprintf(out, warn, params);\n+\t\tfputc('\\n', out);\n+\t}\n+\tva_end(params);\n+}\n+\n /* Only set this, ever, from t/helper/, when verifying that bugs are caught. */\n int BUG_exit_code;\n \n-- \ngitgitgadget\n\n"},{"id":"447513","messageId":"2083fbe9b2e3e1deb94c9903fd0fa2dddd619b3c.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 06/15] diff: allow diff_warn_rename_limit to write somewhere besides stderr","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:32Z","receivedAt":"2022-02-02T07:35:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\ndiff_warn_rename_limit() is hardcoded to write to stderr.  Make it\naccept a file stream parameter to make it more flexible.\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n diff.c            | 13 ++++++++-----\n diff.h            |  3 ++-\n merge-ort.c       |  2 +-\n merge-recursive.c |  3 ++-\n 4 files changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 1bfb01c18ec..28368110147 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6377,17 +6377,20 @@ static const char rename_limit_advice[] =\n N_(\"you may want to set your %s variable to at least \"\n    \"%d and retry the command.\");\n \n-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)\n+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n+\t\t\t    FILE *out)\n {\n \tfflush(stdout);\n \tif (degraded_cc)\n-\t\twarning(_(degrade_cc_to_c_warning));\n+\t\twarning_fp(out, _(degrade_cc_to_c_warning));\n \telse if (needed)\n-\t\twarning(_(rename_limit_warning));\n+\t\twarning_fp(out, _(rename_limit_warning));\n \telse\n \t\treturn;\n+\n+\n \tif (0 < needed)\n-\t\twarning(_(rename_limit_advice), varname, needed);\n+\t\twarning_fp(out, _(rename_limit_advice), varname, needed);\n }\n \n static void create_filepairs_for_header_only_notifications(struct diff_options *o)\n@@ -6870,7 +6873,7 @@ int diff_result_code(struct diff_options *opt, int status)\n \n \tdiff_warn_rename_limit(\"diff.renameLimit\",\n \t\t\t       opt->needed_rename_limit,\n-\t\t\t       opt->degraded_cc_to_c);\n+\t\t\t       opt->degraded_cc_to_c, stderr);\n \tif (!opt->flags.exit_with_status &&\n \t    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))\n \t\treturn status;\ndiff --git a/diff.h b/diff.h\nindex ce9e2cf2e4f..40c5b78fb0a 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -597,7 +597,8 @@ void diffcore_fix_diff_index(void);\n int diff_queue_is_empty(struct diff_options *o);\n void diff_flush(struct diff_options*);\n void diff_free(struct diff_options*);\n-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);\n+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n+\t\t\t    FILE *out);\n \n /* diff-raw status letters */\n #define DIFF_STATUS_ADDED\t\t'A'\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9bf15a01db8..46e72b62880 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4305,7 +4305,7 @@ void merge_switch_to_result(struct merge_options *opt,\n \n \t\t/* Also include needed rename limit adjustment now */\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n+\t\t\t\t       opti->renames.needed_limit, 0, stderr);\n \n \t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n \t}\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 9ec1e6d043a..01ca82773cc 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3738,7 +3738,8 @@ static void merge_finalize(struct merge_options *opt)\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->priv->needed_rename_limit, 0);\n+\t\t\t\t       opt->priv->needed_rename_limit, 0,\n+\t\t\t\t       stderr);\n \tFREE_AND_NULL(opt->priv);\n }\n \n-- \ngitgitgadget\n\n"},{"id":"447514","messageId":"1be858e6aa64e6c04f827bf2cabe39a29283454d.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 07/15] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:33Z","receivedAt":"2022-02-02T07:35:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis patch includes no new code; it simply moves a bunch of lines into a\nnew function.  As such, there are no functional changes.  This is just a\npreparatory step to allow the printed messages to be handled differently\nby other callers, such as in `git merge-tree --write-tree`.\n\n(Patch best viewed with\n     --color-moved --color-moved-ws=allow-indentation-change\n to see that it is a simple code movement.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 77 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 36 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 46e72b62880..82d2faf5bf9 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0, stderr);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4273,42 +4312,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n \n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0, stderr);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"447515","messageId":"04c3bdc44d2c76ffc82a95db3ca4fd07270f94cf.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:34Z","receivedAt":"2022-02-02T07:35:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis modifies the new display_update_messages() function to allow\nprinting to somewhere other than stdout.  It also consolidates the\nlocation of the diff_warn_rename_limit() message with the rest of the\nCONFLICT and other update messages to all go to the same stream.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 9 +++++----\n merge-ort.h | 3 ++-\n 2 files changed, 7 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 82d2faf5bf9..d28d1721d14 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n }\n \n void merge_display_update_messages(struct merge_options *opt,\n-\t\t\t\t   struct merge_result *result)\n+\t\t\t\t   struct merge_result *result,\n+\t\t\t\t   FILE *stream)\n {\n \tstruct merge_options_internal *opti = result->priv;\n \tstruct hashmap_iter iter;\n@@ -4263,13 +4264,13 @@ void merge_display_update_messages(struct merge_options *opt,\n \tfor (i = 0; i < olist.nr; ++i) {\n \t\tstruct strbuf *sb = olist.items[i].util;\n \n-\t\tprintf(\"%s\", sb->buf);\n+\t\tstrbuf_write(sb, stream);\n \t}\n \tstring_list_clear(&olist, 0);\n \n \t/* Also include needed rename limit adjustment now */\n \tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t       opti->renames.needed_limit, 0, stderr);\n+\t\t\t       opti->renames.needed_limit, 0, stream);\n \n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n@@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,\n \t}\n \n \tif (display_update_msgs)\n-\t\tmerge_display_update_messages(opt, result);\n+\t\tmerge_display_update_messages(opt, result, stdout);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..d643b47cb7c 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,\n  * so only call this when bypassing merge_switch_to_result().\n  */\n void merge_display_update_messages(struct merge_options *opt,\n-\t\t\t\t   struct merge_result *result);\n+\t\t\t\t   struct merge_result *result,\n+\t\t\t\t   FILE *stream);\n \n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n-- \ngitgitgadget\n\n"},{"id":"447516","messageId":"c8ed002408d82dc0f1a059a117cd4fd7113d8b07.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 09/15] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:35Z","receivedAt":"2022-02-02T07:35:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, such as:\n  * \"Auto-merging <PATH>\"\n  * \"CONFLICT (content): ...\"\n  * \"CONFLICT (file/directory)\"\n  * etc.\nIn fact, when non-content conflicts occur (such as file/directory,\nmodify/delete, add/add with differing modes, rename/rename (1to2),\netc.), these informational messages may be the only notification the\nuser gets since these conflicts are not representable in the contents\nof the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----\n builtin/merge-tree.c             | 19 ++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 21 +++++++++++++++\n 3 files changed, 76 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 569485815a0..42e0f8f6183 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n@@ -35,17 +35,47 @@ merge is distinguished from a trivial merge in that it includes:\n After the merge completes, it will create a new toplevel tree object.\n See `OUTPUT` below for details.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+By default, for a successful merge, the output from git-merge-tree is\n+simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n+\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -69,7 +99,8 @@ be used as a part of a series of steps such as\n \n However, it does not quite fit into the same category of low-level\n plumbing commands since the possibility of merge conflicts give it a\n-much higher chance of the command not succeeding.\n+much higher chance of the command not succeeding (and NEWTREE containing\n+a bunch of stuff other than just a toplevel tree).\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex d14c9f6e44e..6a556ab1c9c 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -390,6 +390,7 @@ static int trivial_merge(const char *base,\n \n struct merge_tree_options {\n \tint mode;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -432,18 +433,27 @@ static int real_merge(struct merge_tree_options *o,\n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result, stdout);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -453,10 +463,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    'w'),\n \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n \t\t\t    N_(\"do a trivial merge only\"), 't'),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n \t/* Parse arguments */\n+\toriginal_argc = argc;\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n \tif (o.mode) {\n@@ -468,6 +481,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(merge_tree_usage, mt_options);\n \t\to.mode = (argc == 2 ? 'w' : 't');\n \t}\n+\tif (o.mode == 't' && original_argc < argc)\n+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == 'w')\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 66c3eaf2021..e2255711f9c 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -85,4 +85,25 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447517","messageId":"52339b396fac6c3ae7a7dfa42d4d2d6eccc4ede7.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 13/15] merge-tree: allow `ls-files -u` style info to be NUL terminated","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:39Z","receivedAt":"2022-02-02T07:35:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch as `git ls-files` has a -z option, let's add one to merge-tree so\nthat the conflict-info section can be NUL terminated (and avoid quoting\nof unusual filenames).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 21 +++++++++++++----\n builtin/merge-tree.c             |  4 +++-\n t/t4301-merge-tree-write-tree.sh | 40 ++++++++++++++++++++++++++++++++\n 3 files changed, 60 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 55bb7bc61c1..02f766716f9 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -38,6 +38,12 @@ See `OUTPUT` below for details.\n OPTIONS\n -------\n \n+-z::\n+\tDo not quote filenames in the <Conflicted file info> section,\n+\tand end each filename with a NUL character rather than\n+\tnewline.  Also begin the messages section with a NUL character\n+\tinstead of a newline.  See OUTPUT below for more information.\n+\n --exclude-oids-and-modes::\n \tInstead of writing a list of (mode, oid, stage, path) tuples\n \tto output for conflicted files, just provide a list of\n@@ -70,7 +76,8 @@ OID of toplevel tree\n \n This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n-files within this tree may have embedded conflict markers.\n+files within this tree may have embedded conflict markers.  This section\n+is always followed by a newline.\n \n Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n@@ -82,19 +89,25 @@ This is a sequence of lines with the format\n The filename will be quoted as explained for the configuration\n variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n the `--exclude-oids-and-modes` option is passed, the mode, object, and\n-stage will be omitted.\n+stage will be omitted.  If `-z` is passed, the \"lines\" are terminated\n+by a NUL character instead of a newline character.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n-This always starts with a blank line to separate it from the previous\n-sections, and then has free-form messages about the merge, such as:\n+This always starts with a blank line (or NUL if `-z` is passed) to\n+separate it from the previous sections, and then has free-form\n+messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n   * \"Failed to merge submodule <submodule> (<reason>)\"\n   * \"Warning: cannot merge binary files: <filename>\"\n \n+Note that these free-form messages will never have a NUL character\n+in or between them, even if -z is passed.  It is simply a large block\n+of text taking up the remainder of the output.\n+\n EXIT STATUS\n -----------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex dc52cd02dce..7e55f0fa301 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -464,7 +464,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\tstring_list_clear(&conflicted_files, 1);\n \t}\n \tif (o->show_messages) {\n-\t\tprintf(\"\\n\");\n+\t\tputchar(line_termination);\n \t\tmerge_display_update_messages(&opt, &result, stdout);\n \t}\n \tmerge_finalize(&opt, &result);\n@@ -490,6 +490,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), 't'),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_SET_INT('z', NULL, &line_termination,\n+\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n \t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n \t\t\t   &o.exclude_modes_oids_stages,\n \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 1572f460da0..f89d87c26b7 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -139,4 +139,44 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'NUL terminated conflicted file \"lines\"' '\n+\tgit checkout -b tweak1 side1 &&\n+\ttest_write_lines zero 1 2 3 4 5 6 >numbers &&\n+\tgit add numbers &&\n+\tgit mv numbers \"Αυτά μου φαίνονται κινέζικα\" &&\n+\tgit commit -m \"Renamed numbers\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\t#   \"Αυτά μου φαίνονται κινέζικα\" should have a conflict\n+\techo HASH >expect &&\n+\n+\tq_to_tab <<-EOF | lf_to_nul >>expect &&\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~tweak1\n+\t100644 HASH 2Qwhatever~tweak1\n+\t100644 HASH 1QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 2QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 3QΑυτά μου φαίνονται κινέζικα\n+\n+\tEOF\n+\n+\tcat <<-EOF >>expect &&\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n+\tCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n+\tAuto-merging Αυτά μου φαίνονται κινέζικα\n+\tCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447518","messageId":"c854ecb5f4a456cbf3fe89ac4611e301e1ccf0b4.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 14/15] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:40Z","receivedAt":"2022-02-02T07:35:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 02f766716f9..e6a9ff2768b 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -55,6 +55,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n OUTPUT\n ------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 7e55f0fa301..58c0ddc5a32 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -393,6 +393,7 @@ static int trivial_merge(const char *base,\n \n struct merge_tree_options {\n \tint mode;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint exclude_modes_oids_stages;\n };\n@@ -430,7 +431,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common)\n+\tif (!common && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tfor (j = common; j; j = j->next)\n \t\tcommit_list_insert(j->item, &merge_bases);\n@@ -496,6 +497,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.exclude_modes_oids_stages,\n \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex f89d87c26b7..4de089d976d 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -38,7 +38,13 @@ test_expect_success setup '\n \t>whatever/empty &&\n \tgit add numbers greeting whatever/empty &&\n \ttest_tick &&\n-\tgit commit -m other-modifications\n+\tgit commit -m other-modifications &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Content merge and a few conflicts' '\n@@ -179,4 +185,20 @@ test_expect_success 'NUL terminated conflicted file \"lines\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447519","messageId":"bc8591bbb63acc7b4e7b3550e0e6ea1ed5638635.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 15/15] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:41Z","receivedAt":"2022-02-02T07:35:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex e6a9ff2768b..6a2ed475106 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -146,6 +146,52 @@ that they'd have access to if using `git merge`:\n   * any messages that would have been printed to stdout (the <Informational\n     messages>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <Conflicted file info> section instead.  Not\n+only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n+check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <Conflicted file info> list.  The information there is insufficient\n+to do so.  For example: Rename/rename(1to2) conflicts (both sides\n+renamed the same file differently) will result in three different file\n+having higher order stages (but each only has one higher order stage),\n+with no way (short of the <Informational messages> section) to determine\n+which three files are related.  File/directory conflicts also result in\n+a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<Informational messages> section has the necessary info, though it is\n+not designed to be machine parseable.\n+\n+Do NOT assume all filenames listed in the <Informational messages>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <Conflicted file info> and re-merging\n+them to present the conflicts to the user.  This will lose information.\n+Instead, look up the version of the file found within the <OID of\n+toplevel tree> and show that instead.  In particular, the latter will\n+have conflict markers annotated with the original branch/commit being\n+merged and, if renames were involved, the original filename.  While you\n+could include the original branch/commit in the conflict marker\n+annotations when re-merging, the original filename is not available from\n+the <Conflicted file info> and thus you would be losing information that\n+might help the user resolve the conflict.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n"},{"id":"447520","messageId":"1c2a3f5ef63cbc8ac5a61c97e42cc4cce55ee663.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 10/15] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:36Z","receivedAt":"2022-02-02T07:35:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u`, which means\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex d28d1721d14..c4ce6027dc4 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex d643b47cb7c..e635a294ea8 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result,\n \t\t\t\t   FILE *stream);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"447521","messageId":"9c2389eef0e7c8813e0522011132d7f5e530d858.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 11/15] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:37Z","receivedAt":"2022-02-02T07:35:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  8 ++++++++\n builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 11 +++++++++++\n 3 files changed, 41 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 42e0f8f6183..160e8f44b62 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -55,6 +55,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -66,6 +67,13 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 6a556ab1c9c..54dae018203 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -394,7 +397,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *common;\n@@ -438,6 +442,22 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n \t\tprintf(\"\\n\");\n \t\tmerge_display_update_messages(&opt, &result, stdout);\n@@ -486,7 +506,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == 'w')\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex e2255711f9c..7113d060bc5 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -95,6 +95,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -106,4 +108,13 @@ test_expect_success 'test conflict notices and such' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447522","messageId":"2188a8ca1e77f2f5f8249f8b810775205ad529a6.1643787281.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v3 12/15] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-02T07:34:38Z","receivedAt":"2022-02-02T07:35:28Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide an --exclude-modes-oids-stages/-l option for users to exclude\nthe mode, oid, and stage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 30 ++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++++--\n 3 files changed, 58 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 160e8f44b62..55bb7bc61c1 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -38,6 +38,11 @@ See `OUTPUT` below for details.\n OPTIONS\n -------\n \n+--exclude-oids-and-modes::\n+\tInstead of writing a list of (mode, oid, stage, path) tuples\n+\tto output for conflicted files, just provide a list of\n+\tfilenames with conflicts.\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -55,7 +60,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -67,18 +72,23 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--exclude-oids-and-modes` option is passed, the mode, object, and\n+stage will be omitted.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -110,6 +120,14 @@ plumbing commands since the possibility of merge conflicts give it a\n much higher chance of the command not succeeding (and NEWTREE containing\n a bunch of stuff other than just a toplevel tree).\n \n+git-merge-tree was written to provide users with the same information\n+that they'd have access to if using `git merge`:\n+  * what would be written to the working tree (the <OID of toplevel tree>)\n+  * the higher order stages that would be written to the index (the\n+    <Conflicted file info>)\n+  * any messages that would have been printed to stdout (the <Informational\n+    messages>)\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 54dae018203..dc52cd02dce 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -394,6 +394,7 @@ static int trivial_merge(const char *base,\n struct merge_tree_options {\n \tint mode;\n \tint show_messages;\n+\tint exclude_modes_oids_stages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->exclude_modes_oids_stages)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), 't'),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n+\t\t\t   &o.exclude_modes_oids_stages,\n+\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 7113d060bc5..1572f460da0 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -47,6 +47,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(cat .git/AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -86,7 +87,7 @@ test_expect_success 'Barf on too many arguments' '\n '\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \t# Expected results:\n@@ -109,7 +110,7 @@ test_expect_success 'test conflict notices and such' '\n '\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -117,4 +118,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"447564","messageId":"xmqqy22tx8t1.fsf@gitster.g","threadId":"57288","inReplyTo":"02c29f920d0d5fde6d85f7b86a69be92e3f0f34d.1643787281.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:22:50Z","receivedAt":"2022-02-02T21:22:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -392,7 +395,46 @@ struct merge_tree_options {\n>  static int real_merge(struct merge_tree_options *o,\n>  \t\t      const char *branch1, const char *branch2)\n>  {\n> -\tdie(_(\"real merges are not yet implemented\"));\n> +\tstruct commit *parent1, *parent2;\n> +\tstruct commit_list *common;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tstruct commit_list *j;\n> +\tstruct merge_options opt;\n> +\tstruct merge_result result = { 0 };\n> +\n> +\tparent1 = get_merge_parent(branch1);\n> +\tif (!parent1)\n> +\t\thelp_unknown_ref(branch1, \"merge-tree\",\n> +\t\t\t\t _(\"not something we can merge\"));\n> +\n> +\tparent2 = get_merge_parent(branch2);\n> +\tif (!parent2)\n> +\t\thelp_unknown_ref(branch2, \"merge-tree\",\n> +\t\t\t\t _(\"not something we can merge\"));\n> +\n> +\tinit_merge_options(&opt, the_repository);\n> +\n> +\topt.show_rename_progress = 0;\n> +\n> +\topt.branch1 = branch1;\n> +\topt.branch2 = branch2;\n> +\n> +\t/*\n> +\t * Get the merge bases, in reverse order; see comment above\n> +\t * merge_incore_recursive in merge-ort.h\n> +\t */\n> +\tcommon = get_merge_bases(parent1, parent2);\n> +\tif (!common)\n> +\t\tdie(_(\"refusing to merge unrelated histories\"));\n\nIt appears to me that \"merge-tree\" in this mode, with the above\ncode, cannot be used as a workhorse to implement server-side\ncherry-pick (or revert), which needs to allow the user to specify an\narbitrary \"common ancestor\", instead of computing on its own.\n\nTo replay the change made by commit A on top of commit X (i.e.\n\"cherry-pick A on X\"), we have to be able to say \"compute the\nthree-way merge between A and X, pretending as if A^ were their\ncommon ancestor\".  The story is the same for revert---we compute\nthree-way merge between A^ and X, pretending as if A were their\ncommon ancestor.\n\nThe above interface into this function, sadly, does not seem to\nallow such a request, unless I am missing something.\n\nAnd if I am correct, it is a shame---after all, the point of the\nmerge-trees command is to take three trees and run a three-way\nmerge, and not being able to merge three \"trees\" and require\n\"commits\" makes this mode much less useful than its potential.\n"},{"id":"447565","messageId":"xmqqsft1x8g7.fsf@gitster.g","threadId":"57288","inReplyTo":"63f42df21aec5bda50e4414493eb59dcb64e5558.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/13] merge-tree: add option parsing and initial shell for real merge function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:30:32Z","receivedAt":"2022-02-02T21:30:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +struct merge_tree_options {\n> +\tint mode;\n> +};\n\n> +static int real_merge(struct merge_tree_options *o,\n> +\t\t      const char *branch1, const char *branch2)\n> +{\n> +\tdie(_(\"real merges are not yet implemented\"));\n> +}\n> +\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -\tif (argc != 4)\n> -\t\tusage(merge_tree_usage);\n> -\treturn trivial_merge(argc, argv);\n> +\tstruct merge_tree_options o = { 0 };\n> +\tint expected_remaining_argc;\n> +\n> +\tconst char * const merge_tree_usage[] = {\n> +\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> +\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> +\t\tNULL\n> +\t};\n> +\tstruct option mt_options[] = {\n> +\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n> +\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n> +\t\t\t    'w'),\n\nGiven the length of the second line in the usage[] array, it would\nmake more sense to have \"'w'),\" on the same line to make it match\nbetter with the following one.\n\n> +\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n> +\t\t\t    N_(\"do a trivial merge only\"), 't'),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\t/* Parse arguments */\n> +\targc = parse_options(argc, argv, prefix, mt_options,\n> +\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> +\tif (o.mode) {\n> +\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n> +\t\tif (argc != expected_remaining_argc)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t} else {\n> +\t\tif (argc < 2 || argc > 3)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t\to.mode = (argc == 2 ? 'w' : 't');\n> +\t}\n\nWe are not planning to have tons of different command modes, but the\nabove looks quite brittle in assuming that 'w' and 't' are the only\nones, and not having an easy way to extend the part without major\nrewrite when the assumption will have to be broken.  I wonder:\n\n\tswitch (o.cmd_mode) {\n        default:\n\t\tBUG(\"unexpected cmdmode %c\", o.cmd_mode);\n\tcase 0:\n\t\tswitch (argc) {\n\t\tdefault:\n\t\t\tusage_with_options(merge_tree_usage, mt_options);\n\t\tcase 2: \n\t\t\to.cmd_mode = 'w';\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\to.cmd_mode = 't';\n\t\t\tbreak;\n\t\t}\n                expected_remaining_argc = argc;\n\t\tbreak;\n\tcase 'w':\n\t\texpected_remaining_argc = 2;\n                break;\n\tcase 't':\n\t\texpected_remaining_argc = 3;\n \t\tbreak;\n\t}\n\n        if (argc != expected_remaining_argc)\n\t\tusage_with_options(merge_tree_usage, mt_options);\n\neven though it is a tad longer with more boilerplate, is easlier to\nmanage.\n\n> +\t/* Do the relevant type of merge */\n> +\tif (o.mode == 'w')\n> +\t\treturn real_merge(&o, argv[0], argv[1]);\n> +\telse\n> +\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n>  }\n> diff --git a/git.c b/git.c\n> index 5ff21be21f3..6090a1289db 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n> -\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n> +\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n\nThis affects git_support_parseopt_helper function in the completion\nscript, but it by itself is very unlikely to break existing tests on\nthe completion, as the bit only affects how the command line options\nare completed, and the command didn't have any command line options\nto be tested before this series ;-)\n\n>  \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n>  \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n>  \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n"},{"id":"447566","messageId":"xmqqmtj9x8g4.fsf@gitster.g","threadId":"57288","inReplyTo":"02c29f920d0d5fde6d85f7b86a69be92e3f0f34d.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/13] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:30:35Z","receivedAt":"2022-02-02T21:30:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> This adds the ability to perform real merges rather than just trivial\n> merges (meaning handling three way content merges, recursive ancestor\n> consolidation, renames, proper directory/file conflict handling, and so\n> forth).  However, unlike `git merge`, the working tree and index are\n> left alone and no branch is updated.\n>\n> The only output is:\n>   - the toplevel resulting tree printed on stdout\n>   - exit status of 0 (clean), 1 (conflicts present), anything else\n>     (merge could not be performed; unknown if clean or conflicted)\n>\n> This output is meant to be used by some higher level script, perhaps in\n> a sequence of steps like this:\n>\n>    NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n>    test $? -eq 0 || die \"There were conflicts...\"\n>    NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n>    git update-ref $BRANCH1 $NEWCOMMIT\n\nIt is unclear what NEWTREE has, if anything meaningful, when the\ncommand exited with non-zero status.  Let's read on.\n\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index 58731c19422..569485815a0 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -3,26 +3,73 @@ git-merge-tree(1)\n>  \n>  NAME\n>  ----\n> -git-merge-tree - Show three-way merge without touching index\n> +git-merge-tree - Perform merge without touching index or working tree\n\nOK.  It is interesting that both apply equally to either command\nmode ;-)\n\n> +Performs a merge, but does not make any new commits and does not read\n> +from or write to either the working tree or index.\n> +\n> +The second form is deprecated and supported only for backward\n> +compatibility.  It will likely be removed in the future, and will not\n> +be discussed further in this manual.\n\nThis, especially the deletion of the original description on what\ntrivial merge does, may be premature, especially if it is still\n\"supported for backward compatibility\".\n\n> +The first form will merge the two branches, doing a real merge.  A real\n> +merge is distinguished from a trivial merge in that it includes:\n\nI think this is worth keeping, simply because I do not think you\nshould entirely omit description on the trivial one.\n\nBut if we were to remove the description on the trivial one, then it\nis totally meaningless to label the following as \"... distinguished\nfrom a trivial merge in that\".  The list of things the real merge\ncommand mode does (below) is of course worth having.\n\n> +  * three way content merges of individual files\n> +  * rename detection\n> +  * proper directory/file conflict handling\n> +  * recursive ancestor consolidation (i.e. when there is more than one\n> +    merge base, creating a virtual merge base by merging the merge bases)\n> +  * etc.\n> +\n> +After the merge completes, it will create a new toplevel tree object.\n> +See `OUTPUT` below for details.\n> +\n> +OUTPUT\n> +------\n> +\n> +For either a successful or conflicted merge, the output from\n> +git-merge-tree is simply one line:\n> +\n> +\t<OID of toplevel tree>\n> +\n> +The printed tree object corresponds to what would be checked out in\n> +the working tree at the end of `git merge`, and thus may have files\n> +with conflict markers in them.\n\nSo we would leave cruft in the object store, but that is very much\non purpose, and the expectation is that the user would commit-tree\nthe tree object and reference it with a ref soon enough before\ngarbage collection prunes them, just like how write-tree is meant to\nbe used.  OK.\n\n> +EXIT STATUS\n> +-----------\n> +\n> +For a successful, non-conflicted merge, the exit status is 0.  When the\n> +merge has conflicts, the exit status is 1.  If the merge is not able to\n> +complete (or start) due to some kind of error, the exit status is\n> +something other than 0 or 1.\n\nAnd the output given to the standard output stream, when the command\nexits with status higher than 1, is...?  \"unspecified\" is of course\nan acceptable answer, of course, but I am wondering if it is worth\nspelling out in the doc.\n\n> +USAGE NOTES\n> +-----------\n> +\n> +git-merge-tree was written to be low-level plumbing, similar to\n> +hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n\nA notable omission in the above list is 'write-tree'.\n\n> +be used as a part of a series of steps such as\n> +\n> +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> +       test $? -eq 0 || die \"There were conflicts...\"\n> +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n> +       git update-ref $BRANCH1 $NEWCOMMIT\n> +\n> +However, it does not quite fit into the same category of low-level\n> +plumbing commands since the possibility of merge conflicts give it a\n> +much higher chance of the command not succeeding.\n\nI am not sure if that is a fair categorization.  It is a fine\nbuilding block at the lowest level of the tool hierarchy.  The\nprimary thing that differentiates plumbing from Porcelain is that\nthe former is a better fit for scripting: doing one thing and one\nthing well with minimum and stable UI.  The complexity of that one\nthing it does has much less to do with the categorization, and the\nrate at which users may use the command in a failure-inducing\nsituation has nothing to do with it.  The write-tree plumbing\ncommand will reliably fail with 100% chance when the index used as\nits input is unmerged.\n\n> @@ -392,7 +395,46 @@ struct merge_tree_options {\n>  static int real_merge(struct merge_tree_options *o,\n>  \t\t      const char *branch1, const char *branch2)\n>  {\n> -\tdie(_(\"real merges are not yet implemented\"));\n> +\tstruct commit *parent1, *parent2;\n> +\tstruct commit_list *common;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tstruct commit_list *j;\n> +\tstruct merge_options opt;\n> +\tstruct merge_result result = { 0 };\n> +\n> +\tparent1 = get_merge_parent(branch1);\n> +\tif (!parent1)\n> +\t\thelp_unknown_ref(branch1, \"merge-tree\",\n> +\t\t\t\t _(\"not something we can merge\"));\n> +\n> +\tparent2 = get_merge_parent(branch2);\n> +\tif (!parent2)\n> +\t\thelp_unknown_ref(branch2, \"merge-tree\",\n> +\t\t\t\t _(\"not something we can merge\"));\n> +\n> +\tinit_merge_options(&opt, the_repository);\n> +\n> +\topt.show_rename_progress = 0;\n> +\n> +\topt.branch1 = branch1;\n> +\topt.branch2 = branch2;\n> +\n> +\t/*\n> +\t * Get the merge bases, in reverse order; see comment above\n> +\t * merge_incore_recursive in merge-ort.h\n> +\t */\n> +\tcommon = get_merge_bases(parent1, parent2);\n> +\tif (!common)\n> +\t\tdie(_(\"refusing to merge unrelated histories\"));\n> +\tfor (j = common; j; j = j->next)\n> +\t\tcommit_list_insert(j->item, &merge_bases);\n> +\n> +\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> +\tif (result.clean < 0)\n> +\t\tdie(_(\"failure to merge\"));\n> +\tputs(oid_to_hex(&result.tree->object.oid));\n> +\tmerge_finalize(&opt, &result);\n> +\treturn !result.clean; /* result.clean < 0 handled above */\n>  }\n\nThe implementation is rather straight-forward, if you know how to\ndrive the merge_incore_recursive() helper ;-)\n\n> +test_expect_success setup '\n> +\ttest_write_lines 1 2 3 4 5 >numbers &&\n> +\techo hello >greeting &&\n> +\techo foo >whatever &&\n> +\tgit add numbers greeting whatever &&\n> +\ttest_tick &&\n> +\tgit commit -m initial &&\n> +\n> +\tgit branch side1 &&\n> +\tgit branch side2 &&\n> +\n> +\tgit checkout side1 &&\n> +\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n> +\techo hi >greeting &&\n> +\techo bar >whatever &&\n> +\tgit add numbers greeting whatever &&\n> +\ttest_tick &&\n> +\tgit commit -m modify-stuff &&\n> +\n> +\tgit checkout side2 &&\n> +\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n> +\techo yo >greeting &&\n> +\tgit rm whatever &&\n> +\tmkdir whatever &&\n> +\t>whatever/empty &&\n> +\tgit add numbers greeting whatever/empty &&\n> +\ttest_tick &&\n> +\tgit commit -m other-modifications\n> +'\n> +\n> +test_expect_success 'Content merge and a few conflicts' '\n> +\tgit checkout side1^0 &&\n> +\ttest_must_fail git merge side2 &&\n> +\texpected_tree=$(cat .git/AUTO_MERGE) &&\n> +\n> +\t# We will redo the merge, while we are still in a conflicted state!\n> +\ttest_when_finished \"git reset --hard\" &&\n> +\n> +\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n> +\tactual_tree=$(head -n 1 RESULT) &&\n> +\n> +\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n> +\t# exactly match.  Dig into individual files.\n> +\n> +\t# Numbers should have three-way merged cleanly\n> +\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n> +\tgit show ${actual_tree}:numbers >actual &&\n> +\ttest_cmp expect actual &&\n> +\n> +\t# whatever and whatever~<branch> should have same HASHES\n> +\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n> +\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n> +\ttest_cmp expect actual &&\n> +\n> +\t# greeting should have a merge conflict\n> +\tgit show ${expected_tree}:greeting >tmp &&\n> +\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n> +\tgit show ${actual_tree}:greeting >actual &&\n> +\ttest_cmp expect actual\n> +'\n\nIt is somewhat sad that we need to reivent merge test cases over and\nover, instead of easily reuse an existing one by replacing\n\n\tgit checkout one &&\n\tgit merge two\n\nwith\n\n\tgit checkout one &&\n\tT=$(git merge-tree HEAD two) &&\n\tC=$(git commit-tree $T -p HEAD -p two) &&\n\tgit reset --hard $C\n\n;-)\n\n> +> +test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n> +\t# Mis-spell with single \"s\" instead of double \"s\"\n> +\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n> +\n> +\tgrep \"error: unknown option.*mesages\" expect\n> +'\n> +\n> +test_expect_success 'Barf on too many arguments' '\n> +\ttest_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n> +\n> +\tgrep \"^usage: git merge-tree\" expect\n> +'\n\n"},{"id":"447567","messageId":"xmqqh79hx8g1.fsf@gitster.g","threadId":"57288","inReplyTo":"d0d30e92ecd9dff6174a39a94a9e7d7e29896fd4.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 08/13] merge-tree: support including merge messages in output","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:30:38Z","receivedAt":"2022-02-02T21:30:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +By default, for a successful merge, the output from git-merge-tree is\n> +simply one line:\n> +\n> +\t<OID of toplevel tree>\n> +\n> +Whereas for a conflicted merge, the output is by default of the form:\n>  \n>  \t<OID of toplevel tree>\n> +\t<Informational messages>\n\nSounds useful.  This made me wonder, as the only shuffling of the\noutput destination in the past few steps were to send the output to\nsome \"FILE *\", how you send the findings you make while coming up\nwith the result _after_ the result.  It turns out that the ORT\nmachinery already buffers these findings in a strbuf per path, so\nthere is no trouble doing so ;-)\n\nIt still makes me wonder how the \"send rename warnings to the\nstandard output stream, instead of the standard error stream\" change\ninteracts with this change, though.  That needs to be done way\nbefore you finish computing the result, and it does not seem to be\nbuffered in-core, like per-path conflict information messages.\n"},{"id":"447568","messageId":"xmqq35l1x8cm.fsf@gitster.g","threadId":"57288","inReplyTo":"243134dc2478e21f67a6d9cb999d6754b616f6ee.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/13] merge-tree: provide a list of which files have conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:32:41Z","receivedAt":"2022-02-02T21:32:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +Conflicted file list\n> +~~~~~~~~~~~~~~~~~~~~\n> +\n> +This is a sequence of lines containing a filename on each line, quoted\n> +as explained for the configuration variable `core.quotePath` (see\n> +linkgit:git-config[1]).\n\nMakes sense.  Ideally things like this should be discoverable by\ninspecting the tree object shown as the result of the (conflicted)\nmerge, but since the design of the output is to show only a single\ntree, there is nowhere to store such an extra piece of information\nper path (grepping for markers in blobs of course does not count).\n\nI guess an alternative to show four trees when conflicted instead of\none (i.e. the primary tree may either contain only the cleanly\nmerged paths _or_ also blobs with conflict markers for conflicted\npaths; the three other trees record three stages that would be in\nthe index, if we were performing the same merge using the index),\nbut a machine-parseable list of paths is fine.\n\n> +\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n> +\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n> +\t\t\tconst char *name = conflicted_files.items[i].string;\n> +\t\t\tif (last && !strcmp(last, name))\n> +\t\t\t\tcontinue;\n> +\t\t\twrite_name_quoted_relative(\n> +\t\t\t\tname, prefix, stdout, line_termination);\n> +\t\t\tlast = name;\n\nOK.  The iteration used here makes casual readers wonder why the\nhelper doesn't make paths unique, but the string list item holds\nin its util pointer a pointer to a structure with <stage, mode, oid>\ntuple, so it is natural to make the consumer, who wants uniquified\nlist, responsible for deduping, like this loop.\n\n> +\t\t}\n> +\t\tstring_list_clear(&conflicted_files, 1);\n\nAnd the stage-info structure associated with these paths are\ndeallocated with this call.  Good.\n\n> +\t}\n\n"},{"id":"447569","messageId":"xmqqwnidvts1.fsf@gitster.g","threadId":"57288","inReplyTo":"243134dc2478e21f67a6d9cb999d6754b616f6ee.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/13] merge-tree: provide a list of which files have conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:32:46Z","receivedAt":"2022-02-02T21:32:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +Conflicted file list\n> +~~~~~~~~~~~~~~~~~~~~\n> +\n> +This is a sequence of lines containing a filename on each line, quoted\n> +as explained for the configuration variable `core.quotePath` (see\n> +linkgit:git-config[1]).\n\nMakes sense.  Ideally things like this should be discoverable by\ninspecting the tree object shown as the result of the (conflicted)\nmerge, but since the design of the output is to show only a single\ntree, there is nowhere to store such an extra piece of information\nper path (grepping for markers in blobs of course does not count).\n\nI guess an alternative to show four trees when conflicted instead of\none (i.e. the primary tree may either contain only the cleanly\nmerged paths _or_ also blobs with conflict markers for conflicted\npaths; the three other trees record three stages that would be in\nthe index, if we were performing the same merge using the index),\nbut a machine-parseable list of paths is fine.\n\n> +\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n> +\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n> +\t\t\tconst char *name = conflicted_files.items[i].string;\n> +\t\t\tif (last && !strcmp(last, name))\n> +\t\t\t\tcontinue;\n> +\t\t\twrite_name_quoted_relative(\n> +\t\t\t\tname, prefix, stdout, line_termination);\n> +\t\t\tlast = name;\n\nOK.  The iteration used here makes casual readers wonder why the\nhelper doesn't make paths unique, but the string list item holds\nin its util pointer a pointer to a structure with <stage, mode, oid>\ntuple, so it is natural to make the consumer, who wants uniquified\nlist, responsible for deduping, like this loop.\n\n> +\t\t}\n> +\t\tstring_list_clear(&conflicted_files, 1);\n\nAnd the stage-info structure associated with these paths are\ndeallocated with this call.  Good.\n\n> +\t}\n\n"},{"id":"447570","messageId":"xmqqr18lvts0.fsf@gitster.g","threadId":"57288","inReplyTo":"c322e4c6938b7270b6e90998994642074a2813e0.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:32:47Z","receivedAt":"2022-02-02T21:32:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,\n>  \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n>  \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n>  \t\t\tconst char *name = conflicted_files.items[i].string;\n> -\t\t\tif (last && !strcmp(last, name))\n> +\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n> +\t\t\tif (!o->exclude_modes_oids_stages)\n> +\t\t\t\tprintf(\"%06o %s %d\\t\",\n> +\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n> +\t\t\telse if (last && !strcmp(last, name))\n>  \t\t\t\tcontinue;\n>  \t\t\twrite_name_quoted_relative(\n>  \t\t\t\tname, prefix, stdout, line_termination);\n\nOK.  The addition (and disabling of the deduping) is quite trivial.\nWe do not even have to worry about line termination since the extra\npieces of info are prepended to the pathname.  Nice.\n\n> @@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  \t\t\t    N_(\"do a trivial merge only\"), 't'),\n>  \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n>  \t\t\t N_(\"also show informational/conflict messages\")),\n> +\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n> +\t\t\t   &o.exclude_modes_oids_stages,\n> +\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n> +\t\t\t   PARSE_OPT_NONEG),\n\nWhy does \"-l\" give shorter output than without it?  \"-l\" strongly\nhints a longer output than without, at least to me.  Just wondering\nif this will not become a source of confusion to future scripting\nusers.\n\n>  \t\tOPT_END()\n>  \t};\n>  \n"},{"id":"447571","messageId":"xmqqleytvtrz.fsf@gitster.g","threadId":"57288","inReplyTo":"25677d5038cab591774eaa1349365871b23aa2fc.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 12/13] merge-tree: add a --allow-unrelated-histories flag","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T21:32:48Z","receivedAt":"2022-02-02T21:32:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -430,7 +431,7 @@ static int real_merge(struct merge_tree_options *o,\n>  \t * merge_incore_recursive in merge-ort.h\n>  \t */\n>  \tcommon = get_merge_bases(parent1, parent2);\n> -\tif (!common)\n> +\tif (!common && !o->allow_unrelated_histories)\n>  \t\tdie(_(\"refusing to merge unrelated histories\"));\n>  \tfor (j = common; j; j = j->next)\n>  \t\tcommit_list_insert(j->item, &merge_bases);\n\nCurious.  This step _adds_ an \"--allow\" option from the command\nline, but we actually did not have to die() when seeing that there\nis no common ancestor before this step.  The end result is OK either\nway.\n\n> @@ -494,6 +495,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  \t\t\t   &o.exclude_modes_oids_stages,\n>  \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n>  \t\t\t   PARSE_OPT_NONEG),\n> +\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n> +\t\t\t   &o.allow_unrelated_histories,\n> +\t\t\t   N_(\"allow merging unrelated histories\"),\n> +\t\t\t   PARSE_OPT_NONEG),\n>  \t\tOPT_END()\n>  \t};\n"},{"id":"447572","messageId":"CABPp-BGpD6g5QH3=4X_dCuSX0Bs0utHn5hyuU4_UiwNhU0h8sg@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqy22tx8t1.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-02T21:56:56Z","receivedAt":"2022-02-02T21:57:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 1:22 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > @@ -392,7 +395,46 @@ struct merge_tree_options {\n> >  static int real_merge(struct merge_tree_options *o,\n> >                     const char *branch1, const char *branch2)\n> >  {\n> > -     die(_(\"real merges are not yet implemented\"));\n> > +     struct commit *parent1, *parent2;\n> > +     struct commit_list *common;\n> > +     struct commit_list *merge_bases = NULL;\n> > +     struct commit_list *j;\n> > +     struct merge_options opt;\n> > +     struct merge_result result = { 0 };\n> > +\n> > +     parent1 = get_merge_parent(branch1);\n> > +     if (!parent1)\n> > +             help_unknown_ref(branch1, \"merge-tree\",\n> > +                              _(\"not something we can merge\"));\n> > +\n> > +     parent2 = get_merge_parent(branch2);\n> > +     if (!parent2)\n> > +             help_unknown_ref(branch2, \"merge-tree\",\n> > +                              _(\"not something we can merge\"));\n> > +\n> > +     init_merge_options(&opt, the_repository);\n> > +\n> > +     opt.show_rename_progress = 0;\n> > +\n> > +     opt.branch1 = branch1;\n> > +     opt.branch2 = branch2;\n> > +\n> > +     /*\n> > +      * Get the merge bases, in reverse order; see comment above\n> > +      * merge_incore_recursive in merge-ort.h\n> > +      */\n> > +     common = get_merge_bases(parent1, parent2);\n> > +     if (!common)\n> > +             die(_(\"refusing to merge unrelated histories\"));\n>\n> It appears to me that \"merge-tree\" in this mode, with the above\n> code, cannot be used as a workhorse to implement server-side\n> cherry-pick (or revert), which needs to allow the user to specify an\n> arbitrary \"common ancestor\", instead of computing on its own.\n>\n> To replay the change made by commit A on top of commit X (i.e.\n> \"cherry-pick A on X\"), we have to be able to say \"compute the\n> three-way merge between A and X, pretending as if A^ were their\n> common ancestor\".  The story is the same for revert---we compute\n> three-way merge between A^ and X, pretending as if A were their\n> common ancestor.\n>\n> The above interface into this function, sadly, does not seem to\n> allow such a request, unless I am missing something.\n>\n> And if I am correct, it is a shame---after all, the point of the\n> merge-trees command is to take three trees and run a three-way\n> merge, and not being able to merge three \"trees\" and require\n> \"commits\" makes this mode much less useful than its potential.\n\nYes, you are reading right.  I think the cherry-pick/rebase\nreplacement actually deserves a separate command from what merges\nshould use; replaying a sequence of commits just has a number of UI\ndifferences and abilities that I think pull it in a different\ndirection.  I didn't want to wait and submit everything all at once\n(this series is long enough), and I figured that providing the in-core\nequivalent to `git merge` was a simpler first step worth submitting\nfirst before later providing the in-core equivalents to `git\nrebase/git cherry-pick`.\n\n(Also, I'm a bit wary of providing a command meant to just do a single\nthree-way merge with a defined merge-base, because that seems to be a\npath towards returning to a scripted rebase.  Having a way to do only\na single such special merge is fine but we should avoid encouraging\npeople to go down that route.)\n"},{"id":"447575","messageId":"CABPp-BF+LNPFcyj-Ao2nFJw=a9OeX6B-rpbRrn56vOi=h6b9iw@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqmtj9x8g4.fsf@gitster.g","subject":"Re: [PATCH v2 04/13] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-02T22:00:58Z","receivedAt":"2022-02-02T22:01:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 1:30 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > This adds the ability to perform real merges rather than just trivial\n> > merges (meaning handling three way content merges, recursive ancestor\n> > consolidation, renames, proper directory/file conflict handling, and so\n> > forth).  However, unlike `git merge`, the working tree and index are\n> > left alone and no branch is updated.\n> >\n> > The only output is:\n> >   - the toplevel resulting tree printed on stdout\n> >   - exit status of 0 (clean), 1 (conflicts present), anything else\n> >     (merge could not be performed; unknown if clean or conflicted)\n> >\n> > This output is meant to be used by some higher level script, perhaps in\n> > a sequence of steps like this:\n> >\n> >    NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> >    test $? -eq 0 || die \"There were conflicts...\"\n> >    NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n> >    git update-ref $BRANCH1 $NEWCOMMIT\n>\n> It is unclear what NEWTREE has, if anything meaningful, when the\n> command exited with non-zero status.  Let's read on.\n>\n> > diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> > index 58731c19422..569485815a0 100644\n> > --- a/Documentation/git-merge-tree.txt\n> > +++ b/Documentation/git-merge-tree.txt\n> > @@ -3,26 +3,73 @@ git-merge-tree(1)\n> >\n> >  NAME\n> >  ----\n> > -git-merge-tree - Show three-way merge without touching index\n> > +git-merge-tree - Perform merge without touching index or working tree\n>\n> OK.  It is interesting that both apply equally to either command\n> mode ;-)\n>\n> > +Performs a merge, but does not make any new commits and does not read\n> > +from or write to either the working tree or index.\n> > +\n> > +The second form is deprecated and supported only for backward\n> > +compatibility.  It will likely be removed in the future, and will not\n> > +be discussed further in this manual.\n>\n> This, especially the deletion of the original description on what\n> trivial merge does, may be premature, especially if it is still\n> \"supported for backward compatibility\".\n\nI actually extended it, but Dscho suggested removing it entirely --\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2201251804250.2121@tvgsbejvaqbjf.bet/.\nI can restore it; does that paragraph look good to you (you can see\nthe full thing even if it's split by Dscho's commentary).\n\n> > +The first form will merge the two branches, doing a real merge.  A real\n> > +merge is distinguished from a trivial merge in that it includes:\n>\n> I think this is worth keeping, simply because I do not think you\n> should entirely omit description on the trivial one.\n>\n> But if we were to remove the description on the trivial one, then it\n> is totally meaningless to label the following as \"... distinguished\n> from a trivial merge in that\".  The list of things the real merge\n> command mode does (below) is of course worth having.\n>\n> > +  * three way content merges of individual files\n> > +  * rename detection\n> > +  * proper directory/file conflict handling\n> > +  * recursive ancestor consolidation (i.e. when there is more than one\n> > +    merge base, creating a virtual merge base by merging the merge bases)\n> > +  * etc.\n> > +\n> > +After the merge completes, it will create a new toplevel tree object.\n> > +See `OUTPUT` below for details.\n> > +\n> > +OUTPUT\n> > +------\n> > +\n> > +For either a successful or conflicted merge, the output from\n> > +git-merge-tree is simply one line:\n> > +\n> > +     <OID of toplevel tree>\n> > +\n> > +The printed tree object corresponds to what would be checked out in\n> > +the working tree at the end of `git merge`, and thus may have files\n> > +with conflict markers in them.\n>\n> So we would leave cruft in the object store, but that is very much\n> on purpose, and the expectation is that the user would commit-tree\n> the tree object and reference it with a ref soon enough before\n> garbage collection prunes them, just like how write-tree is meant to\n> be used.  OK.\n>\n> > +EXIT STATUS\n> > +-----------\n> > +\n> > +For a successful, non-conflicted merge, the exit status is 0.  When the\n> > +merge has conflicts, the exit status is 1.  If the merge is not able to\n> > +complete (or start) due to some kind of error, the exit status is\n> > +something other than 0 or 1.\n>\n> And the output given to the standard output stream, when the command\n> exits with status higher than 1, is...?  \"unspecified\" is of course\n> an acceptable answer, of course, but I am wondering if it is worth\n> spelling out in the doc.\n\nYeah, \"unspecified\" -- whatever random error the user triggered\n(couldn't parse arguments, the refs you gave don't exist, the repo you\nare running from isn't a repo, disk is full, etc.)\n\n> > +USAGE NOTES\n> > +-----------\n> > +\n> > +git-merge-tree was written to be low-level plumbing, similar to\n> > +hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n>\n> A notable omission in the above list is 'write-tree'.\n\nOoh, yeah, I should add that one.\n\n> > +be used as a part of a series of steps such as\n> > +\n> > +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> > +       test $? -eq 0 || die \"There were conflicts...\"\n> > +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n> > +       git update-ref $BRANCH1 $NEWCOMMIT\n> > +\n> > +However, it does not quite fit into the same category of low-level\n> > +plumbing commands since the possibility of merge conflicts give it a\n> > +much higher chance of the command not succeeding.\n>\n> I am not sure if that is a fair categorization.  It is a fine\n> building block at the lowest level of the tool hierarchy.  The\n> primary thing that differentiates plumbing from Porcelain is that\n> the former is a better fit for scripting: doing one thing and one\n> thing well with minimum and stable UI.  The complexity of that one\n> thing it does has much less to do with the categorization, and the\n> rate at which users may use the command in a failure-inducing\n> situation has nothing to do with it.  The write-tree plumbing\n> command will reliably fail with 100% chance when the index used as\n> its input is unmerged.\n>\n> > @@ -392,7 +395,46 @@ struct merge_tree_options {\n> >  static int real_merge(struct merge_tree_options *o,\n> >                     const char *branch1, const char *branch2)\n> >  {\n> > -     die(_(\"real merges are not yet implemented\"));\n> > +     struct commit *parent1, *parent2;\n> > +     struct commit_list *common;\n> > +     struct commit_list *merge_bases = NULL;\n> > +     struct commit_list *j;\n> > +     struct merge_options opt;\n> > +     struct merge_result result = { 0 };\n> > +\n> > +     parent1 = get_merge_parent(branch1);\n> > +     if (!parent1)\n> > +             help_unknown_ref(branch1, \"merge-tree\",\n> > +                              _(\"not something we can merge\"));\n> > +\n> > +     parent2 = get_merge_parent(branch2);\n> > +     if (!parent2)\n> > +             help_unknown_ref(branch2, \"merge-tree\",\n> > +                              _(\"not something we can merge\"));\n> > +\n> > +     init_merge_options(&opt, the_repository);\n> > +\n> > +     opt.show_rename_progress = 0;\n> > +\n> > +     opt.branch1 = branch1;\n> > +     opt.branch2 = branch2;\n> > +\n> > +     /*\n> > +      * Get the merge bases, in reverse order; see comment above\n> > +      * merge_incore_recursive in merge-ort.h\n> > +      */\n> > +     common = get_merge_bases(parent1, parent2);\n> > +     if (!common)\n> > +             die(_(\"refusing to merge unrelated histories\"));\n> > +     for (j = common; j; j = j->next)\n> > +             commit_list_insert(j->item, &merge_bases);\n> > +\n> > +     merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> > +     if (result.clean < 0)\n> > +             die(_(\"failure to merge\"));\n> > +     puts(oid_to_hex(&result.tree->object.oid));\n> > +     merge_finalize(&opt, &result);\n> > +     return !result.clean; /* result.clean < 0 handled above */\n> >  }\n>\n> The implementation is rather straight-forward, if you know how to\n> drive the merge_incore_recursive() helper ;-)\n\n:-)\n\n>\n> > +test_expect_success setup '\n> > +     test_write_lines 1 2 3 4 5 >numbers &&\n> > +     echo hello >greeting &&\n> > +     echo foo >whatever &&\n> > +     git add numbers greeting whatever &&\n> > +     test_tick &&\n> > +     git commit -m initial &&\n> > +\n> > +     git branch side1 &&\n> > +     git branch side2 &&\n> > +\n> > +     git checkout side1 &&\n> > +     test_write_lines 1 2 3 4 5 6 >numbers &&\n> > +     echo hi >greeting &&\n> > +     echo bar >whatever &&\n> > +     git add numbers greeting whatever &&\n> > +     test_tick &&\n> > +     git commit -m modify-stuff &&\n> > +\n> > +     git checkout side2 &&\n> > +     test_write_lines 0 1 2 3 4 5 >numbers &&\n> > +     echo yo >greeting &&\n> > +     git rm whatever &&\n> > +     mkdir whatever &&\n> > +     >whatever/empty &&\n> > +     git add numbers greeting whatever/empty &&\n> > +     test_tick &&\n> > +     git commit -m other-modifications\n> > +'\n> > +\n> > +test_expect_success 'Content merge and a few conflicts' '\n> > +     git checkout side1^0 &&\n> > +     test_must_fail git merge side2 &&\n> > +     expected_tree=$(cat .git/AUTO_MERGE) &&\n> > +\n> > +     # We will redo the merge, while we are still in a conflicted state!\n> > +     test_when_finished \"git reset --hard\" &&\n> > +\n> > +     test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n> > +     actual_tree=$(head -n 1 RESULT) &&\n> > +\n> > +     # Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n> > +     # exactly match.  Dig into individual files.\n> > +\n> > +     # Numbers should have three-way merged cleanly\n> > +     test_write_lines 0 1 2 3 4 5 6 >expect &&\n> > +     git show ${actual_tree}:numbers >actual &&\n> > +     test_cmp expect actual &&\n> > +\n> > +     # whatever and whatever~<branch> should have same HASHES\n> > +     git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n> > +     git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n> > +     test_cmp expect actual &&\n> > +\n> > +     # greeting should have a merge conflict\n> > +     git show ${expected_tree}:greeting >tmp &&\n> > +     cat tmp | sed -e s/HEAD/side1/ >expect &&\n> > +     git show ${actual_tree}:greeting >actual &&\n> > +     test_cmp expect actual\n> > +'\n>\n> It is somewhat sad that we need to reivent merge test cases over and\n> over, instead of easily reuse an existing one by replacing\n>\n>         git checkout one &&\n>         git merge two\n>\n> with\n>\n>         git checkout one &&\n>         T=$(git merge-tree HEAD two) &&\n>         C=$(git commit-tree $T -p HEAD -p two) &&\n>         git reset --hard $C\n>\n> ;-)\n\nSorry...I'm afraid I'm not following.\n"},{"id":"447576","messageId":"xmqqh79hvsgn.fsf@gitster.g","threadId":"57288","inReplyTo":"CABPp-BGpD6g5QH3=4X_dCuSX0Bs0utHn5hyuU4_UiwNhU0h8sg@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-02T22:01:12Z","receivedAt":"2022-02-02T22:01: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> Yes, you are reading right.  I think the cherry-pick/rebase\n> replacement actually deserves a separate command from what merges\n> should use; replaying a sequence of commits just has a number of UI\n> differences and abilities that I think pull it in a different\n> direction.\n\nI completely disagree.  Each individual step in a sequence of\nreplaying commits in order (or in reverse order) should be\nscriptable as a single merge-tree that takes \"apply the change to go\nfrom A^ to A on X\".  Sequencing and placing UI around it is a job\nfor the script that drives merge-tree.\n"},{"id":"447579","messageId":"CABPp-BHA-w5g5gkVOrxaXEEvDA1jhmge+AA8DOBG-r-o3RwGBQ@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqh79hx8g1.fsf@gitster.g","subject":"Re: [PATCH v2 08/13] merge-tree: support including merge messages in output","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-02T23:09:52Z","receivedAt":"2022-02-02T23:10:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 1:30 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +By default, for a successful merge, the output from git-merge-tree is\n> > +simply one line:\n> > +\n> > +     <OID of toplevel tree>\n> > +\n> > +Whereas for a conflicted merge, the output is by default of the form:\n> >\n> >       <OID of toplevel tree>\n> > +     <Informational messages>\n>\n> Sounds useful.  This made me wonder, as the only shuffling of the\n> output destination in the past few steps were to send the output to\n> some \"FILE *\", how you send the findings you make while coming up\n> with the result _after_ the result.  It turns out that the ORT\n> machinery already buffers these findings in a strbuf per path, so\n> there is no trouble doing so ;-)\n\n:-)\n\n> It still makes me wonder how the \"send rename warnings to the\n> standard output stream, instead of the standard error stream\" change\n> interacts with this change, though.  That needs to be done way\n> before you finish computing the result, and it does not seem to be\n> buffered in-core, like per-path conflict information messages.\n\nActually, that's also stashed away in detect_regular_renames() from merge-ort.c:\n\n    if (diff_opts.needed_rename_limit > renames->needed_limit)\n        renames->needed_limit = diff_opts.needed_rename_limit;\n\nand then the call to diff_warn_rename_limit() is deferred until after\nall the informational messages are printed.  Also, this series\nmodifies diff_warn_rename_limit() later to allow it to send that\nmessage to a different stream.\n"},{"id":"447580","messageId":"CABPp-BGd38Yb_LaJWLG+oiTit0CVRkE-5vmviGfoUOtBFP6yMg@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqr18lvts0.fsf@gitster.g","subject":"Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-02T23:18:54Z","receivedAt":"2022-02-02T23:19:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 1:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > @@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,\n> >               merge_get_conflicted_files(&result, &conflicted_files);\n> >               for (i = 0; i < conflicted_files.nr; i++) {\n> >                       const char *name = conflicted_files.items[i].string;\n> > -                     if (last && !strcmp(last, name))\n> > +                     struct stage_info *c = conflicted_files.items[i].util;\n> > +                     if (!o->exclude_modes_oids_stages)\n> > +                             printf(\"%06o %s %d\\t\",\n> > +                                    c->mode, oid_to_hex(&c->oid), c->stage);\n> > +                     else if (last && !strcmp(last, name))\n> >                               continue;\n> >                       write_name_quoted_relative(\n> >                               name, prefix, stdout, line_termination);\n>\n> OK.  The addition (and disabling of the deduping) is quite trivial.\n> We do not even have to worry about line termination since the extra\n> pieces of info are prepended to the pathname.  Nice.\n>\n> > @@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >                           N_(\"do a trivial merge only\"), 't'),\n> >               OPT_BOOL(0, \"messages\", &o.show_messages,\n> >                        N_(\"also show informational/conflict messages\")),\n> > +             OPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n> > +                        &o.exclude_modes_oids_stages,\n> > +                        N_(\"list conflicted files without modes/oids/stages\"),\n> > +                        PARSE_OPT_NONEG),\n>\n> Why does \"-l\" give shorter output than without it?  \"-l\" strongly\n> hints a longer output than without, at least to me.  Just wondering\n> if this will not become a source of confusion to future scripting\n> users.\n\nHere's another example where I was struggling with naming.  Something\nlike ls-tree's `--name-only` would have been nice, but I was worried\nit'd be confusing since it only affected the conflicted info section\nand does not suppress the printing of the toplevel tree or the\ninformational messages sections.  And the name\n--exclude-modes-oids-stages was long enough that I wanted a short flag\nfor it, and just used the first letter of the description (\"list\nconflicted files...\").  I'm happy to change either the long or the\nshort name for this flag if anyone has suggestions.\n"},{"id":"447582","messageId":"CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqh79hvsgn.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T00:18:39Z","receivedAt":"2022-02-03T00:18:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 2:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Yes, you are reading right.  I think the cherry-pick/rebase\n> > replacement actually deserves a separate command from what merges\n> > should use; replaying a sequence of commits just has a number of UI\n> > differences and abilities that I think pull it in a different\n> > direction.\n>\n> I completely disagree.  Each individual step in a sequence of\n> replaying commits in order (or in reverse order) should be\n> scriptable as a single merge-tree that takes \"apply the change to go\n> from A^ to A on X\".  Sequencing and placing UI around it is a job\n> for the script that drives merge-tree.\n\nAdding such an ability to merge-tree would be trivial -- it basically\ninvolves just two things: (1) accepting one extra argument, and (2)\ncalling merge_incore_nonrecursive() instead of\nmerge_incore_recursive().\n\nHowever, I think forking a subprocess for every merge of a series of\ncommits is a completely unreasonable overhead, so even if we provide\nsuch an option to merge-tree, I still want a separate plumbing-ish\ntool that does non-worktree/non-index replaying of commits which is\nnot written as a driver of merge-tree.  That other tool should just\ncall merge_incore_nonrecursive() directly.  And such a tool, since it\nshould handle an arbitrary number of commits, should certainly be able\nto handle just one commit.  From that angle, it feels like adding\nanother mode to merge-tree would just be a partial duplication of the\nother tool.\n\nHowever, if the other tool doesn't obviate the need for this\nadditional mode (perhaps it ends up being forced to be too\nporcelain-ish insteading of plumbing-ish?), or folks really just want\nanother merge-tree mode, I'm happy to add one along with the tool I\nsubmit later.  Does that sound reasonable to you, or is there\nsomething you're still objecting to that I've missed?\n"},{"id":"447583","messageId":"220203.86mtj87o67.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"2188a8ca1e77f2f5f8249f8b810775205ad529a6.1643787281.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 12/15] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-02T23:55:48Z","receivedAt":"2022-02-03T01:07:51Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Much like `git merge` updates the index with information of the form\n>     (mode, oid, stage, name)\n> provide this output for conflicted files for merge-tree as well.\n> Provide an --exclude-modes-oids-stages/-l option for users to exclude\n\nThis.\n\n> +--exclude-oids-and-modes::\n\nNo longer matches this.\n"},{"id":"447584","messageId":"220203.86iltw7nhm.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BGd38Yb_LaJWLG+oiTit0CVRkE-5vmviGfoUOtBFP6yMg@mail.gmail.com","subject":"Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T01:08:43Z","receivedAt":"2022-02-03T01:22:35Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 02 2022, Elijah Newren wrote:\n\n> On Wed, Feb 2, 2022 at 1:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>> > @@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,\n>> >               merge_get_conflicted_files(&result, &conflicted_files);\n>> >               for (i = 0; i < conflicted_files.nr; i++) {\n>> >                       const char *name = conflicted_files.items[i].string;\n>> > -                     if (last && !strcmp(last, name))\n>> > +                     struct stage_info *c = conflicted_files.items[i].util;\n>> > +                     if (!o->exclude_modes_oids_stages)\n>> > +                             printf(\"%06o %s %d\\t\",\n>> > +                                    c->mode, oid_to_hex(&c->oid), c->stage);\n>> > +                     else if (last && !strcmp(last, name))\n>> >                               continue;\n>> >                       write_name_quoted_relative(\n>> >                               name, prefix, stdout, line_termination);\n>>\n>> OK.  The addition (and disabling of the deduping) is quite trivial.\n>> We do not even have to worry about line termination since the extra\n>> pieces of info are prepended to the pathname.  Nice.\n>>\n>> > @@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>> >                           N_(\"do a trivial merge only\"), 't'),\n>> >               OPT_BOOL(0, \"messages\", &o.show_messages,\n>> >                        N_(\"also show informational/conflict messages\")),\n>> > +             OPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n>> > +                        &o.exclude_modes_oids_stages,\n>> > +                        N_(\"list conflicted files without modes/oids/stages\"),\n>> > +                        PARSE_OPT_NONEG),\n>>\n>> Why does \"-l\" give shorter output than without it?  \"-l\" strongly\n>> hints a longer output than without, at least to me.  Just wondering\n>> if this will not become a source of confusion to future scripting\n>> users.\n>\n> Here's another example where I was struggling with naming.  Something\n> like ls-tree's `--name-only` would have been nice, but I was worried\n> it'd be confusing since it only affected the conflicted info section\n> and does not suppress the printing of the toplevel tree or the\n> informational messages sections.  And the name\n> --exclude-modes-oids-stages was long enough that I wanted a short flag\n> for it, and just used the first letter of the description (\"list\n> conflicted files...\").  I'm happy to change either the long or the\n> short name for this flag if anyone has suggestions.\n\nThere's always sidestepping it by replacing it with a --format :)\n\nAnyway, I'd mentioned that in an earlier review in\n<220124.864k5tigto.gmgdl@evledraar.gmail.com>. FWIW here's an experiment\nto do that that I polished up (mostly copied from the ls-tree WIP code\nI'd written already).\n\nI don't know if it will ever be useful, or if you think it's\nworthwhile/simpler, but in either case I think in doing this I spotted\nthe following issues or otherwise noted inconsistencies in the pre-image:\n\n   The docs say that \"<stage> <path>\" is SP-separated, but it's\n   actually TAB-separated, the rest is SP-separated.\n\n * That you de-dupe --exclude-modes-oids-stages is a bit of a hidden feature,\n   but argubly initiative. Should it by optional? In any case my formatting\n   experiment makes it optional, since it then needs to be generalized to de-dupe\n   after we've formatted.\n\n * Perhaps we should support --abbrev as ls-tree does? The below diff shows\n   it's easy enough.\n\n * The dance you have with sed-ing out the hash in the tests could be made much\n   easier with \"sed 1d <out >actual\" and --no-messages for some existing tests.\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 6a2ed475106..e906d1dc9bf 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -44,10 +44,9 @@ OPTIONS\n \tnewline.  Also begin the messages section with a NUL character\n \tinstead of a newline.  See OUTPUT below for more information.\n \n---exclude-oids-and-modes::\n-\tInstead of writing a list of (mode, oid, stage, path) tuples\n-\tto output for conflicted files, just provide a list of\n-\tfilenames with conflicts.\n+--conflict-format::\n+\tOverride the default \"%(objectmode) %(objectname)\n+\t%(stage)%x09%(path)\" format.\n \n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n@@ -89,13 +88,13 @@ Conflicted file info\n \n This is a sequence of lines with the format\n \n-\t<mode> <object> <stage> <filename>\n+\t%(objectmode) %(objectname) %(stage)%x09%(path)\n \n The filename will be quoted as explained for the configuration\n-variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n-the `--exclude-oids-and-modes` option is passed, the mode, object, and\n-stage will be omitted.  If `-z` is passed, the \"lines\" are terminated\n-by a NUL character instead of a newline character.\n+variable `core.quotePath` (see linkgit:git-config[1]).\n+\n+If `-z` is passed, the \"lines\" are terminated by a NUL character\n+instead of a newline character.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 58c0ddc5a32..14fed95a8ce 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -395,9 +395,64 @@ struct merge_tree_options {\n \tint mode;\n \tint allow_unrelated_histories;\n \tint show_messages;\n-\tint exclude_modes_oids_stages;\n+\tconst char *conflict_format;\n+\tint unique_conflicts;\n+\tint abbrev;\n };\n \n+struct expand_conflict_data {\n+\tconst char *prefix;\n+\tstruct string_list_item *item;\n+\tstruct strbuf *scratch;\n+\tint abbrev;\n+\tstruct strbuf *sb_tmp;\n+};\n+static size_t expand_conflict_format(struct strbuf *sb,\n+\t\t\t\t     const char *start,\n+\t\t\t\t     void *context)\n+{\n+\tstruct expand_conflict_data *data = context;\n+\tstruct string_list_item *item = data->item;\n+\tstruct stage_info *info = item->util;\n+\tconst char *end;\n+\tconst char *p;\n+\tsize_t len;\n+\n+\tlen = strbuf_expand_literal_cb(sb, start, NULL);\n+\tif (len)\n+\t\treturn len;\n+\n+\tif (*start != '(')\n+\t\tdie(_(\"bad format as of '%s'\"), start);\n+\tend = strchr(start + 1, ')');\n+\tif (!end)\n+\t\tdie(_(\"format element '%s' does not end in ')'\"), start);\n+\tlen = end - start + 1;\n+\n+\tif (skip_prefix(start, \"(objectmode)\", &p)) {\n+\t\tstrbuf_addf(sb, \"%06o\", info->mode);\n+\t} else if (skip_prefix(start, \"(objectname)\", &p)) {\n+\t\tstrbuf_addstr(sb, find_unique_abbrev(&info->oid, data->abbrev));\n+\t} else if (skip_prefix(start, \"(stage)\", &p)) {\n+\t\tstrbuf_addf(sb, \"%d\", info->stage);\n+\t} else if (skip_prefix(start, \"(path)\", &p)) {\n+\t\tconst char *name = item->string;\n+\n+\t\tif (data->prefix)\n+\t\t\tname = relative_path(name, data->prefix, data->scratch);\n+\t\tstrbuf_addstr(sb, name);\n+\n+\t\tstrbuf_reset(data->sb_tmp);\n+\t\t/* The relative_path() function resets \"scratch\" */\n+\n+\t} else {\n+\t\tunsigned int errlen = (unsigned long)len;\n+\t\tdie(_(\"bad format specifier %%%.*s\"), errlen, start);\n+\t}\n+\n+\treturn len;\n+}\n+\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2,\n \t\t      const char *prefix)\n@@ -446,23 +501,43 @@ static int real_merge(struct merge_tree_options *o,\n \tputs(oid_to_hex(&result.tree->object.oid));\n \tif (!result.clean) {\n \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n-\t\tconst char *last = NULL;\n-\t\tint i;\n+\t\tstruct string_list_item *item;\n+\t\tchar *last = NULL;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n \n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n-\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n-\t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n-\t\t\tif (!o->exclude_modes_oids_stages)\n-\t\t\t\tprintf(\"%06o %s %d\\t\",\n-\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n-\t\t\telse if (last && !strcmp(last, name))\n+\t\tfor_each_string_list_item(item, &conflicted_files) {\n+\t\t\tstruct expand_conflict_data ctx = {\n+\t\t\t\t.prefix = prefix,\n+\t\t\t\t.item = item,\n+\t\t\t\t.abbrev = o->abbrev,\n+\t\t\t\t.scratch = &sb,\n+\t\t\t\t.sb_tmp = &tmp,\n+\t\t\t};\n+\n+\t\t\tstrbuf_expand(&sb, o->conflict_format, expand_conflict_format, &ctx);\n+\t\t\tstrbuf_addch(&sb, line_termination);\n+\n+\t\t\tif (o->unique_conflicts && last && !strcmp(last, sb.buf)) {\n+\t\t\t\tfree(last);\n+\t\t\t\tlast = strbuf_detach(&sb, NULL);\n \t\t\t\tcontinue;\n-\t\t\twrite_name_quoted_relative(\n-\t\t\t\tname, prefix, stdout, line_termination);\n-\t\t\tlast = name;\n+\t\t\t}\n+\n+\t\t\tfwrite(sb.buf, sb.len, 1, stdout);\n+\n+\t\t\tif (o->unique_conflicts) {\n+\t\t\t\tfree(last);\n+\t\t\t\tlast = strbuf_detach(&sb, NULL);\n+\t\t\t} else {\n+\t\t\t\tstrbuf_reset(&sb);\n+\t\t\t}\n \t\t}\n \t\tstring_list_clear(&conflicted_files, 1);\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&tmp);\n+\t\tfree(last);\n \t}\n \tif (o->show_messages) {\n \t\tputchar(line_termination);\n@@ -474,7 +549,11 @@ static int real_merge(struct merge_tree_options *o,\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { .show_messages = -1 };\n+\tstruct merge_tree_options o = {\n+\t\t.show_messages = -1,\n+\t\t.conflict_format = \"%(objectmode) %(objectname) %(stage)%x09%(path)\",\n+\t\t.unique_conflicts = 1,\n+\t};\n \tint expected_remaining_argc;\n \tint original_argc;\n \n@@ -493,14 +572,15 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_SET_INT('z', NULL, &line_termination,\n \t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n-\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n-\t\t\t   &o.exclude_modes_oids_stages,\n-\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n-\t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_STRING(0, \"conflict-format\", &o.conflict_format, N_(\"format\"),\n+\t\t\t   N_(\"specify a custom format to use for conflicted files\")),\n+\t\tOPT_BOOL(0, \"unique-conflicts\", &o.unique_conflicts,\n+\t\t\t N_(\"omit duplicate --conflict-format lines\")),\n \t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n \t\t\t   &o.allow_unrelated_histories,\n \t\t\t   N_(\"allow merging unrelated histories\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT__ABBREV(&o.abbrev),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 4de089d976d..e6354b2d284 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -93,7 +93,7 @@ test_expect_success 'Barf on too many arguments' '\n '\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \t# Expected results:\n@@ -115,8 +115,35 @@ test_expect_success 'test conflict notices and such' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'merge-tree --unique-conflicts is the default' '\n+\ttest_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" --no-messages side1 side2 >out &&\n+\tsed 1d <out >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tgreeting\n+\twhatever~side1\n+\tEOF\n+\ttest_cmp expect actual &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" --no-messages side1 side2 >out2 &&\n+\tsed 1d <out2 >actual2 &&\n+\ttest_cmp actual actual2\n+'\n+\n+test_expect_success 'merge-tree --no-unique-conflicts' '\n+\ttest_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" --no-unique-conflicts --no-messages side1 side2 >out &&\n+\tsed 1d <out >actual &&\n+\tcat >expect <<-\\EOF &&\n+\tgreeting\n+\tgreeting\n+\tgreeting\n+\twhatever~side1\n+\twhatever~side1\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --conflict-format=\"%(path)\" side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n"},{"id":"447585","messageId":"220203.86ee4k7lo8.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"04c3bdc44d2c76ffc82a95db3ca4fd07270f94cf.1643787281.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T01:48:06Z","receivedAt":"2022-02-03T02:01:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> This modifies the new display_update_messages() function to allow\n> printing to somewhere other than stdout.  It also consolidates the\n> location of the diff_warn_rename_limit() message with the rest of the\n> CONFLICT and other update messages to all go to the same stream.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-ort.c | 9 +++++----\n>  merge-ort.h | 3 ++-\n>  2 files changed, 7 insertions(+), 5 deletions(-)\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 82d2faf5bf9..d28d1721d14 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>  }\n>  \n>  void merge_display_update_messages(struct merge_options *opt,\n> -\t\t\t\t   struct merge_result *result)\n> +\t\t\t\t   struct merge_result *result,\n> +\t\t\t\t   FILE *stream)\n>  {\n>  \tstruct merge_options_internal *opti = result->priv;\n>  \tstruct hashmap_iter iter;\n> @@ -4263,13 +4264,13 @@ void merge_display_update_messages(struct merge_options *opt,\n>  \tfor (i = 0; i < olist.nr; ++i) {\n>  \t\tstruct strbuf *sb = olist.items[i].util;\n>  \n> -\t\tprintf(\"%s\", sb->buf);\n> +\t\tstrbuf_write(sb, stream);\n>  \t}\n>  \tstring_list_clear(&olist, 0);\n>  \n>  \t/* Also include needed rename limit adjustment now */\n>  \tdiff_warn_rename_limit(\"merge.renamelimit\",\n> -\t\t\t       opti->renames.needed_limit, 0, stderr);\n> +\t\t\t       opti->renames.needed_limit, 0, stream);\n\nAt the tip of this series I tried to s/stream/stderr/g this, and\nt4301-merge-tree-write-tree.sh passes, doesn't this warning_fp() special\nbehavior need a test somewhere?\n\nI assumed that warning_fp() would be using vreportf() in usage.c, but\nit's not, it's just falling back to the equivalent of fprintf(out, ...),\nno? I don't really see why 05/15 and parts of 06/15 & this are needed\nover a much simpler simple helper macro like the below. applied on top\nof this series.\n\nI would get it if the point was to actually use the full usage.c\nmachinery, but we're just either calling warning(), or printing a\nformatted string to a file FILE *. There's no need to go through usage.c\nfor that, and adding an API to it that behaves like this new\nwarning_fp() is really confusing.\n\nI.e. an API in usage.c that allowed warning to a given FD would be\ntrying to replace the \"2\" in the write_in_full() call in vreportf(), I\nwould think.\n\ndiff --git a/diff.c b/diff.c\nindex 28368110147..4cf67e93dea 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6377,14 +6377,21 @@ static const char rename_limit_advice[] =\n N_(\"you may want to set your %s variable to at least \"\n    \"%d and retry the command.\");\n \n+#define warning_fp(out, fmt, ...) do { \\\n+\tif (out == stderr) \\\n+\t\twarning(fmt, __VA_ARGS__); \\\n+\telse \\\n+\t\tfprintf(out, fmt, __VA_ARGS__); \\\n+} while (0)\n+\n void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n \t\t\t    FILE *out)\n {\n \tfflush(stdout);\n \tif (degraded_cc)\n-\t\twarning_fp(out, _(degrade_cc_to_c_warning));\n+\t\twarning_fp(out, _(degrade_cc_to_c_warning), NULL);\n \telse if (needed)\n-\t\twarning_fp(out, _(rename_limit_warning));\n+\t\twarning_fp(out, _(rename_limit_warning), NULL);\n \telse\n \t\treturn;\n \ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 64ba60e5c71..d70ce142861 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -475,7 +475,6 @@ int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n-void warning_fp(FILE *out, const char *warn, ...) __attribute__((format (printf, 2, 3)));\n \n #ifndef NO_OPENSSL\n #ifdef APPLE_COMMON_CRYPTO\ndiff --git a/usage.c b/usage.c\nindex 0bfd2c603c0..c7d233b0de9 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -253,20 +253,6 @@ void warning(const char *warn, ...)\n \tva_end(params);\n }\n \n-void warning_fp(FILE *out, const char *warn, ...)\n-{\n-\tva_list params;\n-\n-\tva_start(params, warn);\n-\tif (out == stderr)\n-\t\twarn_routine(warn, params);\n-\telse {\n-\t\tvfprintf(out, warn, params);\n-\t\tfputc('\\n', out);\n-\t}\n-\tva_end(params);\n-}\n-\n /* Only set this, ever, from t/helper/, when verifying that bugs are caught. */\n int BUG_exit_code;\n \n"},{"id":"447586","messageId":"220203.86a6f87lbl.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"63f42df21aec5bda50e4414493eb59dcb64e5558.1643787281.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T02:05:27Z","receivedAt":"2022-02-03T02:09:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Let merge-tree accept a `--write-tree` parameter for choosing real\n> merges instead of trivial merges, and accept an optional\n> `--trivial-merge` option to get the traditional behavior.  Note that\n> these accept different numbers of arguments, though, so these names\n> need not actually be used.\n\nMaybe that ship has sailed, but just my 0.02: I thought this whole thing\nwas much less confusing with your initial merge-tree-ort proposal at\nhttps://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;\nI.e. the end-state of merge-tree.c is that you end up reading largely\nunrelated code (various static functions only used by one side or\nanother).\n\nBut maybe that's all water under the bridge etc, however...\n\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -\tif (argc != 4)\n> -\t\tusage(merge_tree_usage);\n> -\treturn trivial_merge(argc, argv);\n> +\tstruct merge_tree_options o = { 0 };\n> +\tint expected_remaining_argc;\n> +\n> +\tconst char * const merge_tree_usage[] = {\n> +\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> +\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> +\t\tNULL\n> +\t};\n> +\tstruct option mt_options[] = {\n> +\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n> +\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n> +\t\t\t    'w'),\n> +\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n> +\t\t\t    N_(\"do a trivial merge only\"), 't'),\n> +\t\tOPT_END()\n> +\t};\n> +\n> +\t/* Parse arguments */\n> +\targc = parse_options(argc, argv, prefix, mt_options,\n> +\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> +\tif (o.mode) {\n> +\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n> +\t\tif (argc != expected_remaining_argc)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t} else {\n> +\t\tif (argc < 2 || argc > 3)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t\to.mode = (argc == 2 ? 'w' : 't');\n> +\t}\n\nDo we really need to make this interface more special-casey by\nauto-guessing based on argc what argument you want? I.e. instead of\nusage like:\n\n\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n\nWouldn't it be simpler to just have the equivalent of:\n\n\t# old\n        git merge-tree ...\n        # new\n        git merge-tree --new-thing ...\n\nAnd not have to look at ... to figure out if we're dispatching to the\nnew or old thing.\n\n"},{"id":"447588","messageId":"CABPp-BFUqRfptuPgGJ021V-O3SxXFfnHzcPP-qF+g_tsZ53RMQ@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86mtj87o67.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 12/15] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T05:19:01Z","receivedAt":"2022-02-03T05:19:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 5:07 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Much like `git merge` updates the index with information of the form\n> >     (mode, oid, stage, name)\n> > provide this output for conflicted files for merge-tree as well.\n> > Provide an --exclude-modes-oids-stages/-l option for users to exclude\n>\n> This.\n>\n> > +--exclude-oids-and-modes::\n>\n> No longer matches this.\n\nThanks; good catch.\n"},{"id":"447591","messageId":"CABPp-BHKpOPM85bRiYmtZ6YT7AK_jpn6aen+o9jeNM6Jwbns=A@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86iltw7nhm.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T08:39:18Z","receivedAt":"2022-02-03T08:39:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 5:22 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Wed, Feb 02 2022, Elijah Newren wrote:\n>\n> > On Wed, Feb 2, 2022 at 1:32 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >>\n> >> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >>\n> >> > @@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,\n> >> >               merge_get_conflicted_files(&result, &conflicted_files);\n> >> >               for (i = 0; i < conflicted_files.nr; i++) {\n> >> >                       const char *name = conflicted_files.items[i].string;\n> >> > -                     if (last && !strcmp(last, name))\n> >> > +                     struct stage_info *c = conflicted_files.items[i].util;\n> >> > +                     if (!o->exclude_modes_oids_stages)\n> >> > +                             printf(\"%06o %s %d\\t\",\n> >> > +                                    c->mode, oid_to_hex(&c->oid), c->stage);\n> >> > +                     else if (last && !strcmp(last, name))\n> >> >                               continue;\n> >> >                       write_name_quoted_relative(\n> >> >                               name, prefix, stdout, line_termination);\n> >>\n> >> OK.  The addition (and disabling of the deduping) is quite trivial.\n> >> We do not even have to worry about line termination since the extra\n> >> pieces of info are prepended to the pathname.  Nice.\n> >>\n> >> > @@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >> >                           N_(\"do a trivial merge only\"), 't'),\n> >> >               OPT_BOOL(0, \"messages\", &o.show_messages,\n> >> >                        N_(\"also show informational/conflict messages\")),\n> >> > +             OPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n> >> > +                        &o.exclude_modes_oids_stages,\n> >> > +                        N_(\"list conflicted files without modes/oids/stages\"),\n> >> > +                        PARSE_OPT_NONEG),\n> >>\n> >> Why does \"-l\" give shorter output than without it?  \"-l\" strongly\n> >> hints a longer output than without, at least to me.  Just wondering\n> >> if this will not become a source of confusion to future scripting\n> >> users.\n> >\n> > Here's another example where I was struggling with naming.  Something\n> > like ls-tree's `--name-only` would have been nice, but I was worried\n> > it'd be confusing since it only affected the conflicted info section\n> > and does not suppress the printing of the toplevel tree or the\n> > informational messages sections.  And the name\n> > --exclude-modes-oids-stages was long enough that I wanted a short flag\n> > for it, and just used the first letter of the description (\"list\n> > conflicted files...\").  I'm happy to change either the long or the\n> > short name for this flag if anyone has suggestions.\n>\n> There's always sidestepping it by replacing it with a --format :)\n\nAnother solution that occurred to me, and I was _really_ close to\ndoing it for v3, was to just flat drop this patch entirely and not\ninclude any such option.  But...\n\n  * \"Which files had conflicts?\" seems like such an obvious question\n  * I've used `git ls-files -u | awk {print\\$4} | uniq` a lot in the\npast after `git merge` (Or `git rebase`) to get this info (yeah, it\nturns out `git diff --name-only --diff-filter=U` is 4 fewer\ncharacters)\n  * \"display the list of files where conflicts were present in the web\nUI\" was listed as an early usecase[1]\n\n[1] https://lore.kernel.org/git/YYlqpuzv+bmZaFzz@nand.local/\n\nSo it seemed like making that question easy to answer was worthwhile.\n\n> Anyway, I'd mentioned that in an earlier review in\n> <220124.864k5tigto.gmgdl@evledraar.gmail.com>. FWIW here's an experiment\n> to do that that I polished up (mostly copied from the ls-tree WIP code\n> I'd written already).\n>\n> I don't know if it will ever be useful, or if you think it's\n> worthwhile/simpler, but in either case I think in doing this I spotted\n> the following issues or otherwise noted inconsistencies in the pre-image:\n>\n>    The docs say that \"<stage> <path>\" is SP-separated, but it's\n>    actually TAB-separated, the rest is SP-separated.\n\nYeah, good catch.  However, it doesn't actually say they are\nSP-separated; it's ambiguous about the spacing.  Which probably isn't\na good thing, but it was kind of copied from the ls-files manual:\n\n\"\"\"\n       git ls-files just outputs the filenames unless --stage is specified in\n       which case it outputs:\n\n           [<tag> ]<mode> <object> <stage> <file>\n\"\"\"\n\n(which also uses a tab between <stage> and <file> and a space\notherwise, but the output above may lead you to believe otherwise.)\n\n>  * That you de-dupe --exclude-modes-oids-stages is a bit of a hidden feature,\n>    but argubly initiative. Should it by optional? In any case my formatting\n>    experiment makes it optional, since it then needs to be generalized to de-dupe\n>    after we've formatted.\n\nI think without de-duping the flag isn't helpful enough to bother\nimplementing.  Requiring two flags also seems painful, given the\ncommon case scenario.\n\nI hope I'm not coming across as dismissive.  I think eventually adding\na --format and --dedupe (the combination of which might be implied by\nwhatever flag is used now) might be useful additions.  Maybe --abbrev\ntoo...eventually.  But I'm worried that it's distracting from focusing\non usecases.  In particular, I'm worried it leads to \"well, script\nwriters technically can get what they want because we provided\neverything\" rather than focusing on making the most common things easy\nto get, and then extending the command for flexibility as needed\nlater.\n\nI'd really rather that early versions _just_ focus on actual usecases\nas far as UI is concerned (and thus I was really happy to see Dscho\nand Taylor concentrate on that side; I think Christian might have been\ntalking about that angle some but it was hard to differentiate from\nthe \"merge-tree on steroids\" spitballing).  While I want to be careful\nto avoid preventing UI flexibility, I think building it in from the\nbeginning tends to lead to a design that is less usable.  (e.g. the\npossible loss of de-duping that would naturally have arisen from\nlooking at things from the other angle.)  It's just a bias I have.\n\n>  * Perhaps we should support --abbrev as ls-tree does? The below diff shows\n>    it's easy enough.\n\nThis one is less problematic to me, but I'd still rather that the UI\nside of things focused on the usecases for early versions.\n\n>  * The dance you have with sed-ing out the hash in the tests could be made much\n>    easier with \"sed 1d <out >actual\" and --no-messages for some existing tests.\n\nIgnoring the first line is semantically different than verifying it\nlooks like a hash.  It also only works on the first line, and hashes\nappear in multiple places, so you'd need a variety of different sed\ncommands for different parts of the output, which doesn't seem any\neasier at all to me; I think using the same replacement everywhere is\nsimpler.  But perhaps I should turn it into a shell function that I\nuse in each case.\n\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index 6a2ed475106..e906d1dc9bf 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -44,10 +44,9 @@ OPTIONS\n>         newline.  Also begin the messages section with a NUL character\n>         instead of a newline.  See OUTPUT below for more information.\n>\n> ---exclude-oids-and-modes::\n> -       Instead of writing a list of (mode, oid, stage, path) tuples\n> -       to output for conflicted files, just provide a list of\n> -       filenames with conflicts.\n> +--conflict-format::\n> +       Override the default \"%(objectmode) %(objectname)\n> +       %(stage)%x09%(path)\" format.\n>\n>  --[no-]messages::\n>         Write any informational messages such as \"Auto-merging <path>\"\n> @@ -89,13 +88,13 @@ Conflicted file info\n>\n>  This is a sequence of lines with the format\n>\n> -       <mode> <object> <stage> <filename>\n> +       %(objectmode) %(objectname) %(stage)%x09%(path)\n>\n>  The filename will be quoted as explained for the configuration\n> -variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n> -the `--exclude-oids-and-modes` option is passed, the mode, object, and\n> -stage will be omitted.  If `-z` is passed, the \"lines\" are terminated\n> -by a NUL character instead of a newline character.\n> +variable `core.quotePath` (see linkgit:git-config[1]).\n> +\n> +If `-z` is passed, the \"lines\" are terminated by a NUL character\n> +instead of a newline character.\n>\n>  Informational messages\n>  ~~~~~~~~~~~~~~~~~~~~~~\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 58c0ddc5a32..14fed95a8ce 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -395,9 +395,64 @@ struct merge_tree_options {\n>         int mode;\n>         int allow_unrelated_histories;\n>         int show_messages;\n> -       int exclude_modes_oids_stages;\n> +       const char *conflict_format;\n> +       int unique_conflicts;\n> +       int abbrev;\n>  };\n>\n> +struct expand_conflict_data {\n> +       const char *prefix;\n> +       struct string_list_item *item;\n> +       struct strbuf *scratch;\n> +       int abbrev;\n> +       struct strbuf *sb_tmp;\n> +};\n> +static size_t expand_conflict_format(struct strbuf *sb,\n> +                                    const char *start,\n> +                                    void *context)\n> +{\n> +       struct expand_conflict_data *data = context;\n> +       struct string_list_item *item = data->item;\n> +       struct stage_info *info = item->util;\n> +       const char *end;\n> +       const char *p;\n> +       size_t len;\n> +\n> +       len = strbuf_expand_literal_cb(sb, start, NULL);\n> +       if (len)\n> +               return len;\n> +\n> +       if (*start != '(')\n> +               die(_(\"bad format as of '%s'\"), start);\n> +       end = strchr(start + 1, ')');\n> +       if (!end)\n> +               die(_(\"format element '%s' does not end in ')'\"), start);\n> +       len = end - start + 1;\n> +\n> +       if (skip_prefix(start, \"(objectmode)\", &p)) {\n> +               strbuf_addf(sb, \"%06o\", info->mode);\n> +       } else if (skip_prefix(start, \"(objectname)\", &p)) {\n> +               strbuf_addstr(sb, find_unique_abbrev(&info->oid, data->abbrev));\n> +       } else if (skip_prefix(start, \"(stage)\", &p)) {\n> +               strbuf_addf(sb, \"%d\", info->stage);\n> +       } else if (skip_prefix(start, \"(path)\", &p)) {\n> +               const char *name = item->string;\n> +\n> +               if (data->prefix)\n> +                       name = relative_path(name, data->prefix, data->scratch);\n> +               strbuf_addstr(sb, name);\n> +\n> +               strbuf_reset(data->sb_tmp);\n> +               /* The relative_path() function resets \"scratch\" */\n> +\n> +       } else {\n> +               unsigned int errlen = (unsigned long)len;\n> +               die(_(\"bad format specifier %%%.*s\"), errlen, start);\n> +       }\n> +\n> +       return len;\n> +}\n> +\n>  static int real_merge(struct merge_tree_options *o,\n>                       const char *branch1, const char *branch2,\n>                       const char *prefix)\n> @@ -446,23 +501,43 @@ static int real_merge(struct merge_tree_options *o,\n>         puts(oid_to_hex(&result.tree->object.oid));\n>         if (!result.clean) {\n>                 struct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n> -               const char *last = NULL;\n> -               int i;\n> +               struct string_list_item *item;\n> +               char *last = NULL;\n> +               struct strbuf sb = STRBUF_INIT;\n> +               struct strbuf tmp = STRBUF_INIT;\n>\n>                 merge_get_conflicted_files(&result, &conflicted_files);\n> -               for (i = 0; i < conflicted_files.nr; i++) {\n> -                       const char *name = conflicted_files.items[i].string;\n> -                       struct stage_info *c = conflicted_files.items[i].util;\n> -                       if (!o->exclude_modes_oids_stages)\n> -                               printf(\"%06o %s %d\\t\",\n> -                                      c->mode, oid_to_hex(&c->oid), c->stage);\n> -                       else if (last && !strcmp(last, name))\n> +               for_each_string_list_item(item, &conflicted_files) {\n> +                       struct expand_conflict_data ctx = {\n> +                               .prefix = prefix,\n> +                               .item = item,\n> +                               .abbrev = o->abbrev,\n> +                               .scratch = &sb,\n> +                               .sb_tmp = &tmp,\n> +                       };\n> +\n> +                       strbuf_expand(&sb, o->conflict_format, expand_conflict_format, &ctx);\n> +                       strbuf_addch(&sb, line_termination);\n> +\n> +                       if (o->unique_conflicts && last && !strcmp(last, sb.buf)) {\n> +                               free(last);\n> +                               last = strbuf_detach(&sb, NULL);\n>                                 continue;\n> -                       write_name_quoted_relative(\n> -                               name, prefix, stdout, line_termination);\n> -                       last = name;\n> +                       }\n> +\n> +                       fwrite(sb.buf, sb.len, 1, stdout);\n> +\n> +                       if (o->unique_conflicts) {\n> +                               free(last);\n> +                               last = strbuf_detach(&sb, NULL);\n> +                       } else {\n> +                               strbuf_reset(&sb);\n> +                       }\n>                 }\n>                 string_list_clear(&conflicted_files, 1);\n> +               strbuf_release(&sb);\n> +               strbuf_release(&tmp);\n> +               free(last);\n>         }\n>         if (o->show_messages) {\n>                 putchar(line_termination);\n> @@ -474,7 +549,11 @@ static int real_merge(struct merge_tree_options *o,\n>\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -       struct merge_tree_options o = { .show_messages = -1 };\n> +       struct merge_tree_options o = {\n> +               .show_messages = -1,\n> +               .conflict_format = \"%(objectmode) %(objectname) %(stage)%x09%(path)\",\n> +               .unique_conflicts = 1,\n> +       };\n>         int expected_remaining_argc;\n>         int original_argc;\n>\n> @@ -493,14 +572,15 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>                          N_(\"also show informational/conflict messages\")),\n>                 OPT_SET_INT('z', NULL, &line_termination,\n>                             N_(\"separate paths with the NUL character\"), '\\0'),\n> -               OPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n> -                          &o.exclude_modes_oids_stages,\n> -                          N_(\"list conflicted files without modes/oids/stages\"),\n> -                          PARSE_OPT_NONEG),\n> +               OPT_STRING(0, \"conflict-format\", &o.conflict_format, N_(\"format\"),\n> +                          N_(\"specify a custom format to use for conflicted files\")),\n> +               OPT_BOOL(0, \"unique-conflicts\", &o.unique_conflicts,\n> +                        N_(\"omit duplicate --conflict-format lines\")),\n\nThe latter of which you didn't include in the manual?  Also,\nunique_conflicts seems like something that is trivial to understand\nfrom the coding perspective, but probably require quite a bit more\nexplanation from the manual.  For example, if objectname is included\nin the format, unique-conflicts is essentially a no-op.  And that's\nthe default...so, you'd probably have to spend time in the manual\nexplaining under what circumstances it's useful.  I'm also not sure if\na user who wanted (mode, path) would want unique_conflicts to default\nto 1; it may be something only meaningful in the particular case of\n\"just give me conflicted filenames\".\n\n>                 OPT_BOOL_F(0, \"allow-unrelated-histories\",\n>                            &o.allow_unrelated_histories,\n>                            N_(\"allow merging unrelated histories\"),\n>                            PARSE_OPT_NONEG),\n> +               OPT__ABBREV(&o.abbrev),\n>                 OPT_END()\n>         };\n>\n> diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\n> index 4de089d976d..e6354b2d284 100755\n> --- a/t/t4301-merge-tree-write-tree.sh\n> +++ b/t/t4301-merge-tree-write-tree.sh\n> @@ -93,7 +93,7 @@ test_expect_success 'Barf on too many arguments' '\n>  '\n>\n>  test_expect_success 'test conflict notices and such' '\n> -       test_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n> +       test_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" side1 side2 >out &&\n>         sed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n>\n>         # Expected results:\n> @@ -115,8 +115,35 @@ test_expect_success 'test conflict notices and such' '\n>         test_cmp expect actual\n>  '\n>\n> +test_expect_success 'merge-tree --unique-conflicts is the default' '\n> +       test_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" --no-messages side1 side2 >out &&\n> +       sed 1d <out >actual &&\n> +       cat >expect <<-\\EOF &&\n> +       greeting\n> +       whatever~side1\n> +       EOF\n> +       test_cmp expect actual &&\n> +\n> +       test_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" --no-messages side1 side2 >out2 &&\n> +       sed 1d <out2 >actual2 &&\n> +       test_cmp actual actual2\n> +'\n> +\n> +test_expect_success 'merge-tree --no-unique-conflicts' '\n> +       test_expect_code 1 git merge-tree --write-tree --conflict-format=\"%(path)\" --no-unique-conflicts --no-messages side1 side2 >out &&\n> +       sed 1d <out >actual &&\n> +       cat >expect <<-\\EOF &&\n> +       greeting\n> +       greeting\n> +       greeting\n> +       whatever~side1\n> +       whatever~side1\n> +       EOF\n> +       test_cmp expect actual\n> +'\n> +\n>  test_expect_success 'Just the conflicted files without the messages' '\n> -       test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&\n> +       test_expect_code 1 git merge-tree --write-tree --no-messages --conflict-format=\"%(path)\" side1 side2 >out &&\n>         sed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n>\n>         test_write_lines HASH greeting whatever~side1 >expect &&\n"},{"id":"447592","messageId":"CABPp-BHKZnmaq3NM5_D6pwkw2+91EsdJ-uqjfFPBYiUSE28k1g@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86a6f87lbl.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T09:04:01Z","receivedAt":"2022-02-03T09:04:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 6:09 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Let merge-tree accept a `--write-tree` parameter for choosing real\n> > merges instead of trivial merges, and accept an optional\n> > `--trivial-merge` option to get the traditional behavior.  Note that\n> > these accept different numbers of arguments, though, so these names\n> > need not actually be used.\n>\n> Maybe that ship has sailed, but just my 0.02: I thought this whole thing\n> was much less confusing with your initial merge-tree-ort proposal at\n> https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;\n> I.e. the end-state of merge-tree.c is that you end up reading largely\n> unrelated code (various static functions only used by one side or\n> another).\n\nChristian's merge-tree-ort proposal?\n\n> But maybe that's all water under the bridge etc, however...\n>\n> >  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >  {\n> > -     if (argc != 4)\n> > -             usage(merge_tree_usage);\n> > -     return trivial_merge(argc, argv);\n> > +     struct merge_tree_options o = { 0 };\n> > +     int expected_remaining_argc;\n> > +\n> > +     const char * const merge_tree_usage[] = {\n> > +             N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> > +             N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> > +             NULL\n> > +     };\n> > +     struct option mt_options[] = {\n> > +             OPT_CMDMODE(0, \"write-tree\", &o.mode,\n> > +                         N_(\"do a real merge instead of a trivial merge\"),\n> > +                         'w'),\n> > +             OPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n> > +                         N_(\"do a trivial merge only\"), 't'),\n> > +             OPT_END()\n> > +     };\n> > +\n> > +     /* Parse arguments */\n> > +     argc = parse_options(argc, argv, prefix, mt_options,\n> > +                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> > +     if (o.mode) {\n> > +             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n> > +             if (argc != expected_remaining_argc)\n> > +                     usage_with_options(merge_tree_usage, mt_options);\n> > +     } else {\n> > +             if (argc < 2 || argc > 3)\n> > +                     usage_with_options(merge_tree_usage, mt_options);\n> > +             o.mode = (argc == 2 ? 'w' : 't');\n> > +     }\n>\n> Do we really need to make this interface more special-casey by\n> auto-guessing based on argc what argument you want? I.e. instead of\n> usage like:\n>\n>         N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n>         N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>\n> Wouldn't it be simpler to just have the equivalent of:\n>\n>         # old\n>         git merge-tree ...\n>         # new\n>         git merge-tree --new-thing ...\n>\n> And not have to look at ... to figure out if we're dispatching to the\n> new or old thing.\n\nYou seem to be focusing on code simplicity?  Sure, that'd be simpler\ncode, it'd just be a less useful feature.\n\nI think passing --write-tree all the time would be an annoyance.  I\ndon't see why anyone would ever use the other mode.  However, for as\nlong as both exist in the manual, it makes the manual easier to\nexplain to users, and example testcases more self-documenting by\nhaving the flag there.  That's the sole purpose of the flag.\n\nI'm never going to actually use it when I invoke it from the command\nline.  And I suspect most would leave it off.\n"},{"id":"447593","messageId":"CABPp-BHye_Zyw=x8B+QoSxWA1b0xyVL9==7kA4CD0q3eTrk8cQ@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86ee4k7lo8.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T09:12:14Z","receivedAt":"2022-02-03T09:12:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 2, 2022 at 6:01 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > This modifies the new display_update_messages() function to allow\n> > printing to somewhere other than stdout.  It also consolidates the\n> > location of the diff_warn_rename_limit() message with the rest of the\n> > CONFLICT and other update messages to all go to the same stream.\n> >\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n> > ---\n> >  merge-ort.c | 9 +++++----\n> >  merge-ort.h | 3 ++-\n> >  2 files changed, 7 insertions(+), 5 deletions(-)\n> >\n> > diff --git a/merge-ort.c b/merge-ort.c\n> > index 82d2faf5bf9..d28d1721d14 100644\n> > --- a/merge-ort.c\n> > +++ b/merge-ort.c\n> > @@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n> >  }\n> >\n> >  void merge_display_update_messages(struct merge_options *opt,\n> > -                                struct merge_result *result)\n> > +                                struct merge_result *result,\n> > +                                FILE *stream)\n> >  {\n> >       struct merge_options_internal *opti = result->priv;\n> >       struct hashmap_iter iter;\n> > @@ -4263,13 +4264,13 @@ void merge_display_update_messages(struct merge_options *opt,\n> >       for (i = 0; i < olist.nr; ++i) {\n> >               struct strbuf *sb = olist.items[i].util;\n> >\n> > -             printf(\"%s\", sb->buf);\n> > +             strbuf_write(sb, stream);\n> >       }\n> >       string_list_clear(&olist, 0);\n> >\n> >       /* Also include needed rename limit adjustment now */\n> >       diff_warn_rename_limit(\"merge.renamelimit\",\n> > -                            opti->renames.needed_limit, 0, stderr);\n> > +                            opti->renames.needed_limit, 0, stream);\n>\n> At the tip of this series I tried to s/stream/stderr/g this, and\n> t4301-merge-tree-write-tree.sh passes, doesn't this warning_fp() special\n> behavior need a test somewhere?\n\nThat's a fair point, but...this gets back to my cover letter comments\nabout patches 5, 6, and 8.  They implement a code feature that seems\nuseful in general...but which Dscho and Christian didn't like using in\nthis particular command; they just wanted all output on stdout.\n\nSo, it's hard to add a test, because there's no code anywhere that\nexercises it in this series anymore.  I originally wanted this feature\nin my remerge-diff series, but the idea of conflict headers made me\npunt it to this series.  I wanted it for this series, but Dscho and\nChristian didn't.  I could have punted again, but decided the\nunderlying want kept coming up and decided to not excise it --\nespecially since Dscho was helping improve it.  And Junio commented\nthat he liked the idea[1].\n\n[1] https://lore.kernel.org/git/xmqqh79hx8g1.fsf@gitster.g/\n\nBut yeah, it does leave it feeling slightly odd that we implemented a\nfeature that nothing is currently using.  Maybe these 3 should be\nsplit off into their own series?  Still wouldn't have a test yet,\nthough.\n\n> I assumed that warning_fp() would be using vreportf() in usage.c, but\n> it's not, it's just falling back to the equivalent of fprintf(out, ...),\n> no? I don't really see why 05/15 and parts of 06/15 & this are needed\n> over a much simpler simple helper macro like the below. applied on top\n> of this series.\n\nThat macro is simple?  I thought I basically understood Dscho's code,\nbut looking at what you did with diff_warn_rename_limit(), I think I'm\nlost.\n\n> I would get it if the point was to actually use the full usage.c\n> machinery, but we're just either calling warning(), or printing a\n> formatted string to a file FILE *. There's no need to go through usage.c\n> for that, and adding an API to it that behaves like this new\n> warning_fp() is really confusing.\n\nBecause the formatted string being printed to the file won't have the\nsame \"warning: \" prefix that is normally added to stuff in usage?\n\nThat's a fair point; that does have a bit of a consistency problem.\nAnd I'd rather the messages were consistent regardless of where they\nare printed.\n\n> I.e. an API in usage.c that allowed warning to a given FD would be\n> trying to replace the \"2\" in the write_in_full() call in vreportf(), I\n> would think.\n\nHmm, makes sense.\n\n> diff --git a/diff.c b/diff.c\n> index 28368110147..4cf67e93dea 100644\n> --- a/diff.c\n> +++ b/diff.c\n> @@ -6377,14 +6377,21 @@ static const char rename_limit_advice[] =\n>  N_(\"you may want to set your %s variable to at least \"\n>     \"%d and retry the command.\");\n>\n> +#define warning_fp(out, fmt, ...) do { \\\n> +       if (out == stderr) \\\n> +               warning(fmt, __VA_ARGS__); \\\n> +       else \\\n> +               fprintf(out, fmt, __VA_ARGS__); \\\n> +} while (0)\n> +\n>  void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n>                             FILE *out)\n>  {\n>         fflush(stdout);\n>         if (degraded_cc)\n> -               warning_fp(out, _(degrade_cc_to_c_warning));\n> +               warning_fp(out, _(degrade_cc_to_c_warning), NULL);\n>         else if (needed)\n> -               warning_fp(out, _(rename_limit_warning));\n> +               warning_fp(out, _(rename_limit_warning), NULL);\n\nWhy do the only callers have a NULL parameter here?  Is this one of\nthose va_list/va_args things I never bothered to properly learn?\n\n>         else\n>                 return;\n>\n> diff --git a/git-compat-util.h b/git-compat-util.h\n> index 64ba60e5c71..d70ce142861 100644\n> --- a/git-compat-util.h\n> +++ b/git-compat-util.h\n> @@ -475,7 +475,6 @@ int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n>  int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n>  void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n>  void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n> -void warning_fp(FILE *out, const char *warn, ...) __attribute__((format (printf, 2, 3)));\n>\n>  #ifndef NO_OPENSSL\n>  #ifdef APPLE_COMMON_CRYPTO\n> diff --git a/usage.c b/usage.c\n> index 0bfd2c603c0..c7d233b0de9 100644\n> --- a/usage.c\n> +++ b/usage.c\n> @@ -253,20 +253,6 @@ void warning(const char *warn, ...)\n>         va_end(params);\n>  }\n>\n> -void warning_fp(FILE *out, const char *warn, ...)\n> -{\n> -       va_list params;\n> -\n> -       va_start(params, warn);\n> -       if (out == stderr)\n> -               warn_routine(warn, params);\n> -       else {\n> -               vfprintf(out, warn, params);\n> -               fputc('\\n', out);\n> -       }\n> -       va_end(params);\n> -}\n> -\n>  /* Only set this, ever, from t/helper/, when verifying that bugs are caught. */\n>  int BUG_exit_code;\n"},{"id":"447594","messageId":"CABPp-BHZYUmWBvzgFkRYddnUJQWrtah=JJ-yaW9Km8+qWcCfUw@mail.gmail.com","threadId":"57288","inReplyTo":"CABPp-BHKZnmaq3NM5_D6pwkw2+91EsdJ-uqjfFPBYiUSE28k1g@mail.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T09:22:09Z","receivedAt":"2022-02-03T09:22:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 3, 2022 at 1:04 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Wed, Feb 2, 2022 at 6:09 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> >\n> > On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n> >\n> > > From: Elijah Newren <newren@gmail.com>\n> > >\n> > > Let merge-tree accept a `--write-tree` parameter for choosing real\n> > > merges instead of trivial merges, and accept an optional\n> > > `--trivial-merge` option to get the traditional behavior.  Note that\n> > > these accept different numbers of arguments, though, so these names\n> > > need not actually be used.\n> >\n> > Maybe that ship has sailed, but just my 0.02: I thought this whole thing\n> > was much less confusing with your initial merge-tree-ort proposal at\n> > https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;\n> > I.e. the end-state of merge-tree.c is that you end up reading largely\n> > unrelated code (various static functions only used by one side or\n> > another).\n>\n> Christian's merge-tree-ort proposal?\n>\n> > But maybe that's all water under the bridge etc, however...\n> >\n> > >  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> > >  {\n> > > -     if (argc != 4)\n> > > -             usage(merge_tree_usage);\n> > > -     return trivial_merge(argc, argv);\n> > > +     struct merge_tree_options o = { 0 };\n> > > +     int expected_remaining_argc;\n> > > +\n> > > +     const char * const merge_tree_usage[] = {\n> > > +             N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> > > +             N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> > > +             NULL\n> > > +     };\n> > > +     struct option mt_options[] = {\n> > > +             OPT_CMDMODE(0, \"write-tree\", &o.mode,\n> > > +                         N_(\"do a real merge instead of a trivial merge\"),\n> > > +                         'w'),\n> > > +             OPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n> > > +                         N_(\"do a trivial merge only\"), 't'),\n> > > +             OPT_END()\n> > > +     };\n> > > +\n> > > +     /* Parse arguments */\n> > > +     argc = parse_options(argc, argv, prefix, mt_options,\n> > > +                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> > > +     if (o.mode) {\n> > > +             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n> > > +             if (argc != expected_remaining_argc)\n> > > +                     usage_with_options(merge_tree_usage, mt_options);\n> > > +     } else {\n> > > +             if (argc < 2 || argc > 3)\n> > > +                     usage_with_options(merge_tree_usage, mt_options);\n> > > +             o.mode = (argc == 2 ? 'w' : 't');\n> > > +     }\n> >\n> > Do we really need to make this interface more special-casey by\n> > auto-guessing based on argc what argument you want? I.e. instead of\n> > usage like:\n> >\n> >         N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> >         N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> >\n> > Wouldn't it be simpler to just have the equivalent of:\n> >\n> >         # old\n> >         git merge-tree ...\n> >         # new\n> >         git merge-tree --new-thing ...\n> >\n> > And not have to look at ... to figure out if we're dispatching to the\n> > new or old thing.\n>\n> You seem to be focusing on code simplicity?  Sure, that'd be simpler\n> code, it'd just be a less useful feature.\n>\n> I think passing --write-tree all the time would be an annoyance.  I\n> don't see why anyone would ever use the other mode.  However, for as\n> long as both exist in the manual, it makes the manual easier to\n> explain to users, and example testcases more self-documenting by\n> having the flag there.  That's the sole purpose of the flag.\n>\n> I'm never going to actually use it when I invoke it from the command\n> line.  And I suspect most would leave it off.\n\n...also, even if we did require the `--write-tree` flag, we'd still\nhave to look at argc.  Since the option parsing handles both modes,\nsomeone could leave off --write-tree, but include a bunch of other\noptions that only make sense with --write-tree.  Individually checking\nthe setting of every extra flag along with write_tree is a royal pain\nand I don't want to repeat that for each new option added.  Simply\nchecking argc allows you to provide an error message if the user does\nthat.\n\n(And I think it's sad that in Git we often forgot to warn and notify\nusers of options that are only functional with certain other\narguments; it makes it harder for users to figure out, and has in the\npast even made it harder for other developers to figure out what was\nmeant and how things are to be used.  I think I've seen multiple Git\ndevs be confused over ls-files --directory and --no-empty-directory\noptions, assuming they'd do something sensible for tracked files, when\nin fact those arguments are simply ignored because they are only\nmodifiers for how untracked files are treated.)\n"},{"id":"447596","messageId":"220203.86wnic5lba.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BHZYUmWBvzgFkRYddnUJQWrtah=JJ-yaW9Km8+qWcCfUw@mail.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T09:45:56Z","receivedAt":"2022-02-03T09:52:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Feb 03 2022, Elijah Newren wrote:\n\n> On Thu, Feb 3, 2022 at 1:04 AM Elijah Newren <newren@gmail.com> wrote:\n>>\n>> On Wed, Feb 2, 2022 at 6:09 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>> >\n>> > On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n>> >\n>> > > From: Elijah Newren <newren@gmail.com>\n>> > >\n>> > > Let merge-tree accept a `--write-tree` parameter for choosing real\n>> > > merges instead of trivial merges, and accept an optional\n>> > > `--trivial-merge` option to get the traditional behavior.  Note that\n>> > > these accept different numbers of arguments, though, so these names\n>> > > need not actually be used.\n>> >\n>> > Maybe that ship has sailed, but just my 0.02: I thought this whole thing\n>> > was much less confusing with your initial merge-tree-ort proposal at\n>> > https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;\n>> > I.e. the end-state of merge-tree.c is that you end up reading largely\n>> > unrelated code (various static functions only used by one side or\n>> > another).\n>>\n>> Christian's merge-tree-ort proposal?\n>>\n>> > But maybe that's all water under the bridge etc, however...\n>> >\n>> > >  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>> > >  {\n>> > > -     if (argc != 4)\n>> > > -             usage(merge_tree_usage);\n>> > > -     return trivial_merge(argc, argv);\n>> > > +     struct merge_tree_options o = { 0 };\n>> > > +     int expected_remaining_argc;\n>> > > +\n>> > > +     const char * const merge_tree_usage[] = {\n>> > > +             N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n>> > > +             N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>> > > +             NULL\n>> > > +     };\n>> > > +     struct option mt_options[] = {\n>> > > +             OPT_CMDMODE(0, \"write-tree\", &o.mode,\n>> > > +                         N_(\"do a real merge instead of a trivial merge\"),\n>> > > +                         'w'),\n>> > > +             OPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n>> > > +                         N_(\"do a trivial merge only\"), 't'),\n>> > > +             OPT_END()\n>> > > +     };\n>> > > +\n>> > > +     /* Parse arguments */\n>> > > +     argc = parse_options(argc, argv, prefix, mt_options,\n>> > > +                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n>> > > +     if (o.mode) {\n>> > > +             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n>> > > +             if (argc != expected_remaining_argc)\n>> > > +                     usage_with_options(merge_tree_usage, mt_options);\n>> > > +     } else {\n>> > > +             if (argc < 2 || argc > 3)\n>> > > +                     usage_with_options(merge_tree_usage, mt_options);\n>> > > +             o.mode = (argc == 2 ? 'w' : 't');\n>> > > +     }\n>> >\n>> > Do we really need to make this interface more special-casey by\n>> > auto-guessing based on argc what argument you want? I.e. instead of\n>> > usage like:\n>> >\n>> >         N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n>> >         N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>> >\n>> > Wouldn't it be simpler to just have the equivalent of:\n>> >\n>> >         # old\n>> >         git merge-tree ...\n>> >         # new\n>> >         git merge-tree --new-thing ...\n>> >\n>> > And not have to look at ... to figure out if we're dispatching to the\n>> > new or old thing.\n>>\n>> You seem to be focusing on code simplicity?  Sure, that'd be simpler\n>> code, it'd just be a less useful feature.\n>>\n>> I think passing --write-tree all the time would be an annoyance.  I\n>> don't see why anyone would ever use the other mode.  However, for as\n>> long as both exist in the manual, it makes the manual easier to\n>> explain to users, and example testcases more self-documenting by\n>> having the flag there.  That's the sole purpose of the flag.\n>>\n>> I'm never going to actually use it when I invoke it from the command\n>> line.  And I suspect most would leave it off.\n>\n> ...also, even if we did require the `--write-tree` flag, we'd still\n> have to look at argc.  Since the option parsing handles both modes,\n> someone could leave off --write-tree, but include a bunch of other\n> options that only make sense with --write-tree.  Individually checking\n> the setting of every extra flag along with write_tree is a royal pain\n> and I don't want to repeat that for each new option added.  Simply\n> checking argc allows you to provide an error message if the user does\n> that.\n>\n> (And I think it's sad that in Git we often forgot to warn and notify\n> users of options that are only functional with certain other\n> arguments; it makes it harder for users to figure out, and has in the\n> past even made it harder for other developers to figure out what was\n> meant and how things are to be used.  I think I've seen multiple Git\n> devs be confused over ls-files --directory and --no-empty-directory\n> options, assuming they'd do something sensible for tracked files, when\n> in fact those arguments are simply ignored because they are only\n> modifiers for how untracked files are treated.)\n\nThere's a much simpler way to do what you're trying to do here which is\nto only parse --write-tree, and as soon as you have that pass off two\none function or the other, and have those functions call\nparse_options().\n\nThis is how builtin/{bundle,stash,commit-graph}.c etc. solve the same\nproblem. It avoids having to the sort of check you did in 09/15:\n\n\t+\tif (o.mode == 't' && original_argc < argc)\n\t+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n\nThe builtin/submodule--helper.c then does it with a first argument that\n--looks-like-an-option, but is really the same sort of sub-command\nselector.\n\nWhich, at least for me brings this back to liking your merge-tree-ort\n(or merge-tree-ng, merge-tree-new or whatever) better. I.e. both for the\ncode & manual we effectively have two completely different commands here\nanyway. Splitting them up would make both the code simpler, and also\nwhat we have to explain to users.\n\n"},{"id":"447601","messageId":"220203.86o83o5jr2.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BHye_Zyw=x8B+QoSxWA1b0xyVL9==7kA4CD0q3eTrk8cQ@mail.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T10:01:52Z","receivedAt":"2022-02-03T10:26:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Feb 03 2022, Elijah Newren wrote:\n\n> On Wed, Feb 2, 2022 at 6:01 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>>\n>> On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n>>\n>> > From: Elijah Newren <newren@gmail.com>\n>> >\n>> > This modifies the new display_update_messages() function to allow\n>> > printing to somewhere other than stdout.  It also consolidates the\n>> > location of the diff_warn_rename_limit() message with the rest of the\n>> > CONFLICT and other update messages to all go to the same stream.\n>> >\n>> > Signed-off-by: Elijah Newren <newren@gmail.com>\n>> > ---\n>> >  merge-ort.c | 9 +++++----\n>> >  merge-ort.h | 3 ++-\n>> >  2 files changed, 7 insertions(+), 5 deletions(-)\n>> >\n>> > diff --git a/merge-ort.c b/merge-ort.c\n>> > index 82d2faf5bf9..d28d1721d14 100644\n>> > --- a/merge-ort.c\n>> > +++ b/merge-ort.c\n>> > @@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n>> >  }\n>> >\n>> >  void merge_display_update_messages(struct merge_options *opt,\n>> > -                                struct merge_result *result)\n>> > +                                struct merge_result *result,\n>> > +                                FILE *stream)\n>> >  {\n>> >       struct merge_options_internal *opti = result->priv;\n>> >       struct hashmap_iter iter;\n>> > @@ -4263,13 +4264,13 @@ void merge_display_update_messages(struct merge_options *opt,\n>> >       for (i = 0; i < olist.nr; ++i) {\n>> >               struct strbuf *sb = olist.items[i].util;\n>> >\n>> > -             printf(\"%s\", sb->buf);\n>> > +             strbuf_write(sb, stream);\n>> >       }\n>> >       string_list_clear(&olist, 0);\n>> >\n>> >       /* Also include needed rename limit adjustment now */\n>> >       diff_warn_rename_limit(\"merge.renamelimit\",\n>> > -                            opti->renames.needed_limit, 0, stderr);\n>> > +                            opti->renames.needed_limit, 0, stream);\n>>\n>> At the tip of this series I tried to s/stream/stderr/g this, and\n>> t4301-merge-tree-write-tree.sh passes, doesn't this warning_fp() special\n>> behavior need a test somewhere?\n>\n> That's a fair point, but...this gets back to my cover letter comments\n> about patches 5, 6, and 8.  They implement a code feature that seems\n> useful in general...but which Dscho and Christian didn't like using in\n> this particular command; they just wanted all output on stdout.\n>\n> So, it's hard to add a test, because there's no code anywhere that\n> exercises it in this series anymore.  I originally wanted this feature\n> in my remerge-diff series, but the idea of conflict headers made me\n> punt it to this series.  I wanted it for this series, but Dscho and\n> Christian didn't.  I could have punted again, but decided the\n> underlying want kept coming up and decided to not excise it --\n> especially since Dscho was helping improve it.  And Junio commented\n> that he liked the idea[1].\n>\n> [1] https://lore.kernel.org/git/xmqqh79hx8g1.fsf@gitster.g/\n>\n> But yeah, it does leave it feeling slightly odd that we implemented a\n> feature that nothing is currently using.  Maybe these 3 should be\n> split off into their own series?  Still wouldn't have a test yet,\n> though.\n>\n>> I assumed that warning_fp() would be using vreportf() in usage.c, but\n>> it's not, it's just falling back to the equivalent of fprintf(out, ...),\n>> no? I don't really see why 05/15 and parts of 06/15 & this are needed\n>> over a much simpler simple helper macro like the below. applied on top\n>> of this series.\n>\n> That macro is simple?  I thought I basically understood Dscho's code,\n> but looking at what you did with diff_warn_rename_limit(), I think I'm\n> lost.\n\nI guess that's a matter of taste, yeah it's a bit of macro soup if\nyou're not familiar with it. FWIW (sans bug I noted below) it's the\nmacro soup we already use for other functions in usage.c.\n\n>> I would get it if the point was to actually use the full usage.c\n>> machinery, but we're just either calling warning(), or printing a\n>> formatted string to a file FILE *. There's no need to go through usage.c\n>> for that, and adding an API to it that behaves like this new\n>> warning_fp() is really confusing.\n>\n> Because the formatted string being printed to the file won't have the\n> same \"warning: \" prefix that is normally added to stuff in usage?\n\nBut the pre-image doesn't add that either. We're just calling\nvfprintf(), not our own vreportf().\n\n> That's a fair point; that does have a bit of a consistency problem.\n> And I'd rather the messages were consistent regardless of where they\n> are printed.\n\nI think that makes sense, that's why I added die_message() recently. If\nyou meant to print a \"warning: \" prefix I think it would also be fine in\nthis case to just do it inline. See prior art at:\n\n    git grep '\"(fatal|error|warning):' -- '*.c'\n\n>> I.e. an API in usage.c that allowed warning to a given FD would be\n>> trying to replace the \"2\" in the write_in_full() call in vreportf(), I\n>> would think.\n>\n> Hmm, makes sense.\n\nThe reason I'm barking up this particular tree is that I've got some\nupcoming patches for usage.c (the C99-only macro series):\nhttps://lore.kernel.org/git/RFC-cover-00.21-00000000000-20211115T220831Z-avarab@gmail.com/\n\nIt would need to deal with anything in the API. In this case there's not\nmuch to deal with, since it's really not at all using the rest of\nusage.c, it's just a \"or to stderr\".\n\n>> diff --git a/diff.c b/diff.c\n>> index 28368110147..4cf67e93dea 100644\n>> --- a/diff.c\n>> +++ b/diff.c\n>> @@ -6377,14 +6377,21 @@ static const char rename_limit_advice[] =\n>>  N_(\"you may want to set your %s variable to at least \"\n>>     \"%d and retry the command.\");\n>>\n>> +#define warning_fp(out, fmt, ...) do { \\\n>> +       if (out == stderr) \\\n>> +               warning(fmt, __VA_ARGS__); \\\n>> +       else \\\n>> +               fprintf(out, fmt, __VA_ARGS__); \\\n>> +} while (0)\n>> +\n>>  void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n>>                             FILE *out)\n>>  {\n>>         fflush(stdout);\n>>         if (degraded_cc)\n>> -               warning_fp(out, _(degrade_cc_to_c_warning));\n>> +               warning_fp(out, _(degrade_cc_to_c_warning), NULL);\n>>         else if (needed)\n>> -               warning_fp(out, _(rename_limit_warning));\n>> +               warning_fp(out, _(rename_limit_warning), NULL);\n>\n> Why do the only callers have a NULL parameter here?  Is this one of\n> those va_list/va_args things I never bothered to properly learn?\n\nThat's wrong (I blame tiredness last night),an actual working version is\nproduced below. Clang accepted my broken code, but gcc rightly yells\nabout it:\n\ndiff --git a/diff.c b/diff.c\nindex 28368110147..a2bc2595533 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6377,6 +6377,13 @@ static const char rename_limit_advice[] =\n N_(\"you may want to set your %s variable to at least \"\n    \"%d and retry the command.\");\n \n+#define warning_fp(out, ...) do { \\\n+\tif (out == stderr) \\\n+\t\twarning(__VA_ARGS__); \\\n+\telse \\\n+\t\tfprintf(out, __VA_ARGS__); \\\n+} while (0)\n+\n void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n \t\t\t    FILE *out)\n {\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 64ba60e5c71..d70ce142861 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -475,7 +475,6 @@ int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n-void warning_fp(FILE *out, const char *warn, ...) __attribute__((format (printf, 2, 3)));\n \n #ifndef NO_OPENSSL\n #ifdef APPLE_COMMON_CRYPTO\ndiff --git a/usage.c b/usage.c\nindex 0bfd2c603c0..c7d233b0de9 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -253,20 +253,6 @@ void warning(const char *warn, ...)\n \tva_end(params);\n }\n \n-void warning_fp(FILE *out, const char *warn, ...)\n-{\n-\tva_list params;\n-\n-\tva_start(params, warn);\n-\tif (out == stderr)\n-\t\twarn_routine(warn, params);\n-\telse {\n-\t\tvfprintf(out, warn, params);\n-\t\tfputc('\\n', out);\n-\t}\n-\tva_end(params);\n-}\n-\n /* Only set this, ever, from t/helper/, when verifying that bugs are caught. */\n int BUG_exit_code;\n \nI do think you'd probably prefer the non-macro version, which is pretty\nmuch just going back to this:\nhttps://lore.kernel.org/git/6fb4f4580a581b2e43bc4b8deaa3d2d2bf4a8756.1643479633.git.gitgitgadget@gmail.com/\n\ndiff --git a/diff.c b/diff.c\nindex 28368110147..21c9561f546 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6380,17 +6380,28 @@ N_(\"you may want to set your %s variable to at least \"\n void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n \t\t\t    FILE *out)\n {\n+\tconst char *msg;\n+\n \tfflush(stdout);\n \tif (degraded_cc)\n-\t\twarning_fp(out, _(degrade_cc_to_c_warning));\n+\t\tmsg = _(degrade_cc_to_c_warning);\n \telse if (needed)\n-\t\twarning_fp(out, _(rename_limit_warning));\n+\t\tmsg = _(rename_limit_warning);\n \telse\n \t\treturn;\n \n+\tif (out == stderr)\n+\t\twarning(\"%s\", msg);\n+\telse\n+\t\tfprintf(stderr, \"%s\", msg);\n \n-\tif (0 < needed)\n-\t\twarning_fp(out, _(rename_limit_advice), varname, needed);\n+\tif (0 >= needed)\n+\t\treturn;\n+\n+\tif (out == stderr)\n+\t\twarning(_(rename_limit_advice), varname, needed);\n+\telse\n+\t\tfprintf(stderr, _(rename_limit_advice), varname, needed);\n }\n \n static void create_filepairs_for_header_only_notifications(struct diff_options *o)\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 64ba60e5c71..d70ce142861 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -475,7 +475,6 @@ int error(const char *err, ...) __attribute__((format (printf, 1, 2)));\n int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));\n void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2)));\n-void warning_fp(FILE *out, const char *warn, ...) __attribute__((format (printf, 2, 3)));\n \n #ifndef NO_OPENSSL\n #ifdef APPLE_COMMON_CRYPTO\ndiff --git a/usage.c b/usage.c\nindex 0bfd2c603c0..c7d233b0de9 100644\n--- a/usage.c\n+++ b/usage.c\n@@ -253,20 +253,6 @@ void warning(const char *warn, ...)\n \tva_end(params);\n }\n \n-void warning_fp(FILE *out, const char *warn, ...)\n-{\n-\tva_list params;\n-\n-\tva_start(params, warn);\n-\tif (out == stderr)\n-\t\twarn_routine(warn, params);\n-\telse {\n-\t\tvfprintf(out, warn, params);\n-\t\tfputc('\\n', out);\n-\t}\n-\tva_end(params);\n-}\n-\n /* Only set this, ever, from t/helper/, when verifying that bugs are caught. */\n int BUG_exit_code;\n \nNote that both your pre-image, my macro version and Johannes's\nlinked-to-above are technically buggy in that they treat a\nnon-formatting format as a formatting format. I.e. we should use\nwarning(\"%s\", msg) in that case, not warning(msg).\n\nSee 927dc330705 (advice.h: add missing __attribute__((format)) & fix\nusage, 2021-07-13) for a similar bug/fix.\n"},{"id":"447602","messageId":"220203.86k0ec5jl7.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BHKZnmaq3NM5_D6pwkw2+91EsdJ-uqjfFPBYiUSE28k1g@mail.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T10:26:27Z","receivedAt":"2022-02-03T10:29:45Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Feb 03 2022, Elijah Newren wrote:\n\n> On Wed, Feb 2, 2022 at 6:09 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>>\n>> On Wed, Feb 02 2022, Elijah Newren via GitGitGadget wrote:\n>>\n>> > From: Elijah Newren <newren@gmail.com>\n>> >\n>> > Let merge-tree accept a `--write-tree` parameter for choosing real\n>> > merges instead of trivial merges, and accept an optional\n>> > `--trivial-merge` option to get the traditional behavior.  Note that\n>> > these accept different numbers of arguments, though, so these names\n>> > need not actually be used.\n>>\n>> Maybe that ship has sailed, but just my 0.02: I thought this whole thing\n>> was much less confusing with your initial merge-tree-ort proposal at\n>> https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;\n>> I.e. the end-state of merge-tree.c is that you end up reading largely\n>> unrelated code (various static functions only used by one side or\n>> another).\n>\n> Christian's merge-tree-ort proposal?\n\nYes. I'm clearly getting the chronology wrong here. Sorry.\n\n>> But maybe that's all water under the bridge etc, however...\n>>\n>> >  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>> >  {\n>> > -     if (argc != 4)\n>> > -             usage(merge_tree_usage);\n>> > -     return trivial_merge(argc, argv);\n>> > +     struct merge_tree_options o = { 0 };\n>> > +     int expected_remaining_argc;\n>> > +\n>> > +     const char * const merge_tree_usage[] = {\n>> > +             N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n>> > +             N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>> > +             NULL\n>> > +     };\n>> > +     struct option mt_options[] = {\n>> > +             OPT_CMDMODE(0, \"write-tree\", &o.mode,\n>> > +                         N_(\"do a real merge instead of a trivial merge\"),\n>> > +                         'w'),\n>> > +             OPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n>> > +                         N_(\"do a trivial merge only\"), 't'),\n>> > +             OPT_END()\n>> > +     };\n>> > +\n>> > +     /* Parse arguments */\n>> > +     argc = parse_options(argc, argv, prefix, mt_options,\n>> > +                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n>> > +     if (o.mode) {\n>> > +             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n>> > +             if (argc != expected_remaining_argc)\n>> > +                     usage_with_options(merge_tree_usage, mt_options);\n>> > +     } else {\n>> > +             if (argc < 2 || argc > 3)\n>> > +                     usage_with_options(merge_tree_usage, mt_options);\n>> > +             o.mode = (argc == 2 ? 'w' : 't');\n>> > +     }\n>>\n>> Do we really need to make this interface more special-casey by\n>> auto-guessing based on argc what argument you want? I.e. instead of\n>> usage like:\n>>\n>>         N_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n>>         N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>>\n>> Wouldn't it be simpler to just have the equivalent of:\n>>\n>>         # old\n>>         git merge-tree ...\n>>         # new\n>>         git merge-tree --new-thing ...\n>>\n>> And not have to look at ... to figure out if we're dispatching to the\n>> new or old thing.\n>\n> You seem to be focusing on code simplicity?  Sure, that'd be simpler\n> code, it'd just be a less useful feature.\n>\n> I think passing --write-tree all the time would be an annoyance.  I\n> don't see why anyone would ever use the other mode.  However, for as\n> long as both exist in the manual, it makes the manual easier to\n> explain to users, and example testcases more self-documenting by\n> having the flag there.  That's the sole purpose of the flag.\n>\n> I'm never going to actually use it when I invoke it from the command\n> line.  And I suspect most would leave it off.\n\nI think I mostly covered this in\nhttps://lore.kernel.org/git/220203.86wnic5lba.gmgdl@evledraar.gmail.com/\nin the side-thread.\n\nI'm not opposed to them being in the same command, it just seems like\nmore work for those reasons in every way.\n\nE.g. the current manual page doesn't really describe the format the\n--trivial-merge emits, so we could do with a section on that, but then\nit would say \"only for this option, not the other\" etc.\n"},{"id":"447603","messageId":"20220203104241.yvfragan6ucecfjl@gmail.com","threadId":"57288","inReplyTo":"CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Johannes Altmanninger","fromEmail":"aclopte@gmail.com","sentAt":"2022-02-03T10:42:41Z","receivedAt":"2022-02-03T10:42:56Z","isPatch":true,"sender":{"key":"aclopte@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6853872?v=4"},"body":"On Wed, Feb 02, 2022 at 04:18:39PM -0800, Elijah Newren wrote:\n> On Wed, Feb 2, 2022 at 2:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > Elijah Newren <newren@gmail.com> writes:\n> >\n> > > Yes, you are reading right.  I think the cherry-pick/rebase\n> > > replacement actually deserves a separate command from what merges\n> > > should use; replaying a sequence of commits just has a number of UI\n> > > differences and abilities that I think pull it in a different\n> > > direction.\n> >\n> > I completely disagree.  Each individual step in a sequence of\n> > replaying commits in order (or in reverse order) should be\n> > scriptable as a single merge-tree that takes \"apply the change to go\n> > from A^ to A on X\".  Sequencing and placing UI around it is a job\n> > for the script that drives merge-tree.\n> \n> Adding such an ability to merge-tree would be trivial -- it basically\n> involves just two things: (1) accepting one extra argument, and (2)\n> calling merge_incore_nonrecursive() instead of\n> merge_incore_recursive().\n> \n> However, I think forking a subprocess for every merge of a series of\n> commits is a completely unreasonable overhead, so even if we provide\n> such an option to merge-tree, I still want a separate plumbing-ish\n> tool that does non-worktree/non-index replaying of commits which is\n> not written as a driver of merge-tree.  That other tool should just\n> call merge_incore_nonrecursive() directly.  And such a tool, since it\n> should handle an arbitrary number of commits, should certainly be able\n> to handle just one commit.  From that angle, it feels like adding\n> another mode to merge-tree would just be a partial duplication of the\n> other tool.\n\nI wonder how the UI of a tool that does non-worktree/non-index cherry-picks\nwill look like.  I'd expect it to produce the same output as merge-tree,\nexcept cherry-pick should probably output a commit OID, not a tree.\n\nMaybe we want a unified command that produces commits from any sequence of\nmerge/cherry-pick/revert/reword steps. The obvious UI would use something\nlike the rebase-todo list as input.  For example:\n\n\t$ echo '\n\tpick commit1\n\treword commit2\t# edit commit message in $GIT_EDITOR\n\tmerge commit3 -m \"log message\"\n\t' | git create-commit commit0\n\t<OID of final commit>\n\nwe start from commit0 and apply steps one-by-one. Obviously, one unsolved\nproblem is how to pass parameters like commit messages if no editor should\nbe invoked (my sketch uses -m).\nIf any of the steps fails when merging merge, then we get the tree with\nconflicts\n\n\t$ echo '\n\tpick commit1\n\tpick commit2\n\tpick commit-that-does-not-apply\n\t' | git create-commit commit0\n\t<OID of commit after step 2>\n\t<OID of toplevel tree after failed merge>\n\t<Conflicted file info>\n\t<Informational messages>\n\nReplaying a series of commits might look like this:\n\n\t$ echo 'pick commit1 ^commit0' | git create-commit new-base\n\nI'm concluding that this is a difficult UI problem, and having a merge-tree\ncommand that accepts a \"common ancestor\" parameter could make it easier\nto experiment.  Of course that depends on who is experimenting.\n\n> \n> However, if the other tool doesn't obviate the need for this\n> additional mode (perhaps it ends up being forced to be too\n> porcelain-ish insteading of plumbing-ish?), or folks really just want\n> another merge-tree mode, I'm happy to add one along with the tool I\n> submit later.  Does that sound reasonable to you, or is there\n> something you're still objecting to that I've missed?\n"},{"id":"447617","messageId":"CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86o83o5jr2.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T16:09:42Z","receivedAt":"2022-02-03T16:09:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 3, 2022 at 2:26 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Thu, Feb 03 2022, Elijah Newren wrote:\n>\n> > On Wed, Feb 2, 2022 at 6:01 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n[...]\n> >> I would get it if the point was to actually use the full usage.c\n> >> machinery, but we're just either calling warning(), or printing a\n> >> formatted string to a file FILE *. There's no need to go through usage.c\n> >> for that, and adding an API to it that behaves like this new\n> >> warning_fp() is really confusing.\n> >\n> > Because the formatted string being printed to the file won't have the\n> > same \"warning: \" prefix that is normally added to stuff in usage?\n>\n> But the pre-image doesn't add that either. We're just calling\n> vfprintf(), not our own vreportf().\n\nRight, I'm saying that I thought you were reporting the original patch\nas buggy because it doesn't produce the same message when given a\ndifferent stream; it'll omit the \"warning: \" prefix.  And I was\nagreeing that it was buggy for those reasons.\n\nOr was there a different reason you didn't like that function being in usage.c?\n\n> > That's a fair point; that does have a bit of a consistency problem.\n> > And I'd rather the messages were consistent regardless of where they\n> > are printed.\n>\n> I think that makes sense, that's why I added die_message() recently. If\n> you meant to print a \"warning: \" prefix I think it would also be fine in\n> this case to just do it inline. See prior art at:\n>\n>     git grep '\"(fatal|error|warning):' -- '*.c'\n\nSo, making diff_warn_rename_limit() stop using warning(), and just\nalways directly writing out and including \"warning:\" in its message?\n\nI'm wondering if that might cause problems if there are any existing\ncallers of diff_warn_rename_limit() that might also be using\nset_warn_routine() (e.g. perhaps apply.c?).  Of course, those callers\nprobably couldn't handle anything other than the default stream.\nHmm...\n\n> >> diff --git a/diff.c b/diff.c\n> >> index 28368110147..4cf67e93dea 100644\n> >> --- a/diff.c\n> >> +++ b/diff.c\n> >> @@ -6377,14 +6377,21 @@ static const char rename_limit_advice[] =\n> >>  N_(\"you may want to set your %s variable to at least \"\n> >>     \"%d and retry the command.\");\n> >>\n> >> +#define warning_fp(out, fmt, ...) do { \\\n> >> +       if (out == stderr) \\\n> >> +               warning(fmt, __VA_ARGS__); \\\n> >> +       else \\\n> >> +               fprintf(out, fmt, __VA_ARGS__); \\\n> >> +} while (0)\n> >> +\n> >>  void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n> >>                             FILE *out)\n> >>  {\n> >>         fflush(stdout);\n> >>         if (degraded_cc)\n> >> -               warning_fp(out, _(degrade_cc_to_c_warning));\n> >> +               warning_fp(out, _(degrade_cc_to_c_warning), NULL);\n> >>         else if (needed)\n> >> -               warning_fp(out, _(rename_limit_warning));\n> >> +               warning_fp(out, _(rename_limit_warning), NULL);\n> >\n> > Why do the only callers have a NULL parameter here?  Is this one of\n> > those va_list/va_args things I never bothered to properly learn?\n>\n> That's wrong (I blame tiredness last night),an actual working version is\n> produced below. Clang accepted my broken code, but gcc rightly yells\n> about it:\n\nWell, seeing the new code makes me feel better as it makes more sense\nto me now.  ;-)\n\n> Note that both your pre-image, my macro version and Johannes's\n> linked-to-above are technically buggy in that they treat a\n> non-formatting format as a formatting format. I.e. we should use\n> warning(\"%s\", msg) in that case, not warning(msg).\n>\n> See 927dc330705 (advice.h: add missing __attribute__((format)) & fix\n> usage, 2021-07-13) for a similar bug/fix.\n\nGood point.\n\n\nMan, what a can of worms this all is.  Maybe I really should just drop\npatches 5, 6, and 8 for now...\n"},{"id":"447618","messageId":"CABPp-BF8VoQ7F7yvfzrpQEZwErxHzb9x8M_R9PrrM7vWzw=wSw@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86wnic5lba.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T16:20:35Z","receivedAt":"2022-02-03T16:20:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 3, 2022 at 1:52 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Thu, Feb 03 2022, Elijah Newren wrote:\n>\n[...]\n> > ...also, even if we did require the `--write-tree` flag, we'd still\n> > have to look at argc.  Since the option parsing handles both modes,\n> > someone could leave off --write-tree, but include a bunch of other\n> > options that only make sense with --write-tree.  Individually checking\n> > the setting of every extra flag along with write_tree is a royal pain\n> > and I don't want to repeat that for each new option added.  Simply\n> > checking argc allows you to provide an error message if the user does\n> > that.\n> >\n> > (And I think it's sad that in Git we often forgot to warn and notify\n> > users of options that are only functional with certain other\n> > arguments; it makes it harder for users to figure out, and has in the\n> > past even made it harder for other developers to figure out what was\n> > meant and how things are to be used.  I think I've seen multiple Git\n> > devs be confused over ls-files --directory and --no-empty-directory\n> > options, assuming they'd do something sensible for tracked files, when\n> > in fact those arguments are simply ignored because they are only\n> > modifiers for how untracked files are treated.)\n>\n> There's a much simpler way to do what you're trying to do here which is\n> to only parse --write-tree, and as soon as you have that pass off two\n> one function or the other, and have those functions call\n> parse_options().\n\nBut that makes --write-tree a mandatory argument when trying to use\nthat mode, right?  If so, that is not a simpler way to do what I'm\ntrying to do at all; it breaks my intended usage.\n\n--write-tree is a documentation-only construct that users should never\nhave to pass.\n\nAlso, what happens if we remove the --trivial-merge flag and its whole\nmode after a sufficient deprecation period?  Would the --write-tree\nparameter remain required in your model to select the only existing\nmode, simply due to us having gone through a transition period?\n"},{"id":"447619","messageId":"220203.86fsoz6hr3.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T16:19:15Z","receivedAt":"2022-02-03T16:24:06Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Feb 03 2022, Elijah Newren wrote:\n\n> On Thu, Feb 3, 2022 at 2:26 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>>\n>> On Thu, Feb 03 2022, Elijah Newren wrote:\n>>\n>> > On Wed, Feb 2, 2022 at 6:01 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> [...]\n>> >> I would get it if the point was to actually use the full usage.c\n>> >> machinery, but we're just either calling warning(), or printing a\n>> >> formatted string to a file FILE *. There's no need to go through usage.c\n>> >> for that, and adding an API to it that behaves like this new\n>> >> warning_fp() is really confusing.\n>> >\n>> > Because the formatted string being printed to the file won't have the\n>> > same \"warning: \" prefix that is normally added to stuff in usage?\n>>\n>> But the pre-image doesn't add that either. We're just calling\n>> vfprintf(), not our own vreportf().\n>\n> Right, I'm saying that I thought you were reporting the original patch\n> as buggy because it doesn't produce the same message when given a\n> different stream; it'll omit the \"warning: \" prefix.  And I was\n> agreeing that it was buggy for those reasons.\n>\n> Or was there a different reason you didn't like that function being in usage.c?\n\nMaybe it was accidentally a bug report :) But no, I was just observing\nthat it was odd that it was in usage.c when it seemingly had almost\nnothing to do with what that API accomplishes.\n\nBut maybe the underlying issue is that the \"warning: \" part is missing\nhere. But I didn't mean to report that/missed it.\n\n>> > That's a fair point; that does have a bit of a consistency problem.\n>> > And I'd rather the messages were consistent regardless of where they\n>> > are printed.\n>>\n>> I think that makes sense, that's why I added die_message() recently. If\n>> you meant to print a \"warning: \" prefix I think it would also be fine in\n>> this case to just do it inline. See prior art at:\n>>\n>>     git grep '\"(fatal|error|warning):' -- '*.c'\n>\n> So, making diff_warn_rename_limit() stop using warning(), and just\n> always directly writing out and including \"warning:\" in its message?\n>\n> I'm wondering if that might cause problems if there are any existing\n> callers of diff_warn_rename_limit() that might also be using\n> set_warn_routine() (e.g. perhaps apply.c?).  Of course, those callers\n> probably couldn't handle anything other than the default stream.\n> Hmm...\n\nUsing set_warn_routine() is the \"right\" way to do it currently, and with\nor without a \"warning: \" prefix the current API use is \"wrong\" if the\npurpose is to have it behave nicely with the pluggable usage.c API.\n\nBut of course that may not be the goal at all, i.e. I think here we've\nprobably stopped caring about usage.c's formatting, logging\netc. entirely, and are just emitting a string.\n\nJust like serve.c emits \"E <msg>\" or whatever (and not with error()).\n\n>> >> diff --git a/diff.c b/diff.c\n>> >> index 28368110147..4cf67e93dea 100644\n>> >> --- a/diff.c\n>> >> +++ b/diff.c\n>> >> @@ -6377,14 +6377,21 @@ static const char rename_limit_advice[] =\n>> >>  N_(\"you may want to set your %s variable to at least \"\n>> >>     \"%d and retry the command.\");\n>> >>\n>> >> +#define warning_fp(out, fmt, ...) do { \\\n>> >> +       if (out == stderr) \\\n>> >> +               warning(fmt, __VA_ARGS__); \\\n>> >> +       else \\\n>> >> +               fprintf(out, fmt, __VA_ARGS__); \\\n>> >> +} while (0)\n>> >> +\n>> >>  void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n>> >>                             FILE *out)\n>> >>  {\n>> >>         fflush(stdout);\n>> >>         if (degraded_cc)\n>> >> -               warning_fp(out, _(degrade_cc_to_c_warning));\n>> >> +               warning_fp(out, _(degrade_cc_to_c_warning), NULL);\n>> >>         else if (needed)\n>> >> -               warning_fp(out, _(rename_limit_warning));\n>> >> +               warning_fp(out, _(rename_limit_warning), NULL);\n>> >\n>> > Why do the only callers have a NULL parameter here?  Is this one of\n>> > those va_list/va_args things I never bothered to properly learn?\n>>\n>> That's wrong (I blame tiredness last night),an actual working version is\n>> produced below. Clang accepted my broken code, but gcc rightly yells\n>> about it:\n>\n> Well, seeing the new code makes me feel better as it makes more sense\n> to me now.  ;-)\n>\n>> Note that both your pre-image, my macro version and Johannes's\n>> linked-to-above are technically buggy in that they treat a\n>> non-formatting format as a formatting format. I.e. we should use\n>> warning(\"%s\", msg) in that case, not warning(msg).\n>>\n>> See 927dc330705 (advice.h: add missing __attribute__((format)) & fix\n>> usage, 2021-07-13) for a similar bug/fix.\n>\n> Good point.\n>\n> Man, what a can of worms this all is.  Maybe I really should just drop\n> patches 5, 6, and 8 for now...\n\nYeah, I really think it's worth it to just sprinkle a tiny bit of\nif/else (or a macro) here and print to stderr inline or not. We can make\nsome use of some usage.c when there's good reason to do so, but this bit\njust seems like a needless digression.\n\nI hope all of this has helped somewhat ...\n"},{"id":"447620","messageId":"CABPp-BH_TiJaDpn2+VVjCb83NEFjL9teSk06+YiZyFGiTu8Lpg@mail.gmail.com","threadId":"57288","inReplyTo":"20220203104241.yvfragan6ucecfjl@gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T16:54:52Z","receivedAt":"2022-02-03T16:55:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Thu, Feb 3, 2022 at 2:42 AM Johannes Altmanninger <aclopte@gmail.com> wrote:\n>\n> On Wed, Feb 02, 2022 at 04:18:39PM -0800, Elijah Newren wrote:\n> > On Wed, Feb 2, 2022 at 2:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > >\n> > > Elijah Newren <newren@gmail.com> writes:\n> > >\n> > > > Yes, you are reading right.  I think the cherry-pick/rebase\n> > > > replacement actually deserves a separate command from what merges\n> > > > should use; replaying a sequence of commits just has a number of UI\n> > > > differences and abilities that I think pull it in a different\n> > > > direction.\n> > >\n> > > I completely disagree.  Each individual step in a sequence of\n> > > replaying commits in order (or in reverse order) should be\n> > > scriptable as a single merge-tree that takes \"apply the change to go\n> > > from A^ to A on X\".  Sequencing and placing UI around it is a job\n> > > for the script that drives merge-tree.\n> >\n> > Adding such an ability to merge-tree would be trivial -- it basically\n> > involves just two things: (1) accepting one extra argument, and (2)\n> > calling merge_incore_nonrecursive() instead of\n> > merge_incore_recursive().\n> >\n> > However, I think forking a subprocess for every merge of a series of\n> > commits is a completely unreasonable overhead, so even if we provide\n> > such an option to merge-tree, I still want a separate plumbing-ish\n> > tool that does non-worktree/non-index replaying of commits which is\n> > not written as a driver of merge-tree.  That other tool should just\n> > call merge_incore_nonrecursive() directly.  And such a tool, since it\n> > should handle an arbitrary number of commits, should certainly be able\n> > to handle just one commit.  From that angle, it feels like adding\n> > another mode to merge-tree would just be a partial duplication of the\n> > other tool.\n>\n> I wonder how the UI of a tool that does non-worktree/non-index cherry-picks\n> will look like.  I'd expect it to produce the same output as merge-tree,\n> except cherry-pick should probably output a commit OID, not a tree.\n>\n> Maybe we want a unified command that produces commits from any sequence of\n> merge/cherry-pick/revert/reword steps. The obvious UI would use something\n> like the rebase-todo list as input.  For example:\n>\n>         $ echo '\n>         pick commit1\n>         reword commit2  # edit commit message in $GIT_EDITOR\n>         merge commit3 -m \"log message\"\n>         ' | git create-commit commit0\n>         <OID of final commit>\n>\n> we start from commit0 and apply steps one-by-one. Obviously, one unsolved\n> problem is how to pass parameters like commit messages if no editor should\n> be invoked (my sketch uses -m).\n> If any of the steps fails when merging merge, then we get the tree with\n> conflicts\n>\n>         $ echo '\n>         pick commit1\n>         pick commit2\n>         pick commit-that-does-not-apply\n>         ' | git create-commit commit0\n>         <OID of commit after step 2>\n>         <OID of toplevel tree after failed merge>\n>         <Conflicted file info>\n>         <Informational messages>\n>\n> Replaying a series of commits might look like this:\n>\n>         $ echo 'pick commit1 ^commit0' | git create-commit new-base\n>\n> I'm concluding that this is a difficult UI problem\n\nI agree.  I've got a lot of thoughts on it, and some work in progress\ntowards it (https://github.com/newren/git/tree/replay -- _very_ hacky,\nnot even close to alpha quality, lots of fixup commits, todo comments,\nrandom brain dump files added to the tree, based on a previous round\nof this patch series, not updated for weeks, etc., etc.)\n\n> and having a merge-tree\n> command that accepts a \"common ancestor\" parameter could make it easier\n> to experiment.  Of course that depends on who is experimenting.\n\nI think that would result in experiments and eventually full-blown\nscripts designed around forking subprocesses for every merge, and\npushes us back into the world of having a scripted-rebase again.  Yes,\nI know people can transliterate shell back to C; it seems to always be\ndone as a half-way measure with the forking just being done from C or\nhave other UI-warts guided by the shell design.  In fact, *that* was\nthe primary reason for me not providing a merge-tree option based on\nmerge_incore_nonrecursive(), despite how trivial it'd be to provide\nit.  If someone wanted a merge_incore_nonrecursive() mode for\nmerge-tree for reasons other than attempting to build a\nrebase/cherry-pick replacement based on it, then I'd be much happier\nto provide it.\n\nIf someone wants to experiment with what a plumbing-ish\nrebase/cherry-pick would look like, the _right_ way to do it would be\nmaking using of merge_incore_nonrecursive() directly.  If they want\nexample code, I already provided some a year and a half ago and got it\nmerged into git.git in the form of t/helper/test-fast-rebase.c.  My\n\"replay\" branch is based on that code, but (a) moves it from t/helper\nto a real builtin, (b) removes the hardcoded very strict input, (c)\nremoves the line of code doing the index & working tree updates, and\n(d) modifies the output to be a more plumbing-ish style.\n\nWe'll certainly have discussions on what that should look like.  But a\nplumbing-ish replacement for merge was much simpler, and made sense to\ndo first.  I would prefer to concentrate on getting that hammered down\nfirst.  Then I'll start discussions on a plumbing-ish\nrebase/cherry-pick.  And if that doesn't fulfill all the needs that\nfolks think they want out of merge-tree, then we can add a\nmerge_incore_nonrecursive()-based mode to merge-tree.  It's all\ncoming, but having fought transliterations-of-scripts in\nmerge-recursive.c, sequencer.c, stash.c, rebase.c, etc. for years I\nreally, really don't want any more of that.  Let's end that insanity.\n"},{"id":"447621","messageId":"CABPp-BFFcFxWL+FRSf9ANwHU1mp_oWcsfLOwvBAuv-J3oNh3SA@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86fsoz6hr3.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T17:00:22Z","receivedAt":"2022-02-03T17:00:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 3, 2022 at 8:24 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Thu, Feb 03 2022, Elijah Newren wrote:\n>\n> > On Thu, Feb 3, 2022 at 2:26 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> >>\n> >> On Thu, Feb 03 2022, Elijah Newren wrote:\n> >>\n> >> > On Wed, Feb 2, 2022 at 6:01 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> > [...]\n> >> >> I would get it if the point was to actually use the full usage.c\n> >> >> machinery, but we're just either calling warning(), or printing a\n> >> >> formatted string to a file FILE *. There's no need to go through usage.c\n> >> >> for that, and adding an API to it that behaves like this new\n> >> >> warning_fp() is really confusing.\n> >> >\n> >> > Because the formatted string being printed to the file won't have the\n> >> > same \"warning: \" prefix that is normally added to stuff in usage?\n> >>\n> >> But the pre-image doesn't add that either. We're just calling\n> >> vfprintf(), not our own vreportf().\n> >\n> > Right, I'm saying that I thought you were reporting the original patch\n> > as buggy because it doesn't produce the same message when given a\n> > different stream; it'll omit the \"warning: \" prefix.  And I was\n> > agreeing that it was buggy for those reasons.\n> >\n> > Or was there a different reason you didn't like that function being in usage.c?\n>\n> Maybe it was accidentally a bug report :) But no, I was just observing\n> that it was odd that it was in usage.c when it seemingly had almost\n> nothing to do with what that API accomplishes.\n>\n> But maybe the underlying issue is that the \"warning: \" part is missing\n> here. But I didn't mean to report that/missed it.\n>\n> >> > That's a fair point; that does have a bit of a consistency problem.\n> >> > And I'd rather the messages were consistent regardless of where they\n> >> > are printed.\n> >>\n> >> I think that makes sense, that's why I added die_message() recently. If\n> >> you meant to print a \"warning: \" prefix I think it would also be fine in\n> >> this case to just do it inline. See prior art at:\n> >>\n> >>     git grep '\"(fatal|error|warning):' -- '*.c'\n> >\n> > So, making diff_warn_rename_limit() stop using warning(), and just\n> > always directly writing out and including \"warning:\" in its message?\n> >\n> > I'm wondering if that might cause problems if there are any existing\n> > callers of diff_warn_rename_limit() that might also be using\n> > set_warn_routine() (e.g. perhaps apply.c?).  Of course, those callers\n> > probably couldn't handle anything other than the default stream.\n> > Hmm...\n>\n> Using set_warn_routine() is the \"right\" way to do it currently, and with\n> or without a \"warning: \" prefix the current API use is \"wrong\" if the\n> purpose is to have it behave nicely with the pluggable usage.c API.\n>\n> But of course that may not be the goal at all, i.e. I think here we've\n> probably stopped caring about usage.c's formatting, logging\n> etc. entirely, and are just emitting a string.\n>\n> Just like serve.c emits \"E <msg>\" or whatever (and not with error()).\n>\n> >> >> diff --git a/diff.c b/diff.c\n> >> >> index 28368110147..4cf67e93dea 100644\n> >> >> --- a/diff.c\n> >> >> +++ b/diff.c\n> >> >> @@ -6377,14 +6377,21 @@ static const char rename_limit_advice[] =\n> >> >>  N_(\"you may want to set your %s variable to at least \"\n> >> >>     \"%d and retry the command.\");\n> >> >>\n> >> >> +#define warning_fp(out, fmt, ...) do { \\\n> >> >> +       if (out == stderr) \\\n> >> >> +               warning(fmt, __VA_ARGS__); \\\n> >> >> +       else \\\n> >> >> +               fprintf(out, fmt, __VA_ARGS__); \\\n> >> >> +} while (0)\n> >> >> +\n> >> >>  void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,\n> >> >>                             FILE *out)\n> >> >>  {\n> >> >>         fflush(stdout);\n> >> >>         if (degraded_cc)\n> >> >> -               warning_fp(out, _(degrade_cc_to_c_warning));\n> >> >> +               warning_fp(out, _(degrade_cc_to_c_warning), NULL);\n> >> >>         else if (needed)\n> >> >> -               warning_fp(out, _(rename_limit_warning));\n> >> >> +               warning_fp(out, _(rename_limit_warning), NULL);\n> >> >\n> >> > Why do the only callers have a NULL parameter here?  Is this one of\n> >> > those va_list/va_args things I never bothered to properly learn?\n> >>\n> >> That's wrong (I blame tiredness last night),an actual working version is\n> >> produced below. Clang accepted my broken code, but gcc rightly yells\n> >> about it:\n> >\n> > Well, seeing the new code makes me feel better as it makes more sense\n> > to me now.  ;-)\n> >\n> >> Note that both your pre-image, my macro version and Johannes's\n> >> linked-to-above are technically buggy in that they treat a\n> >> non-formatting format as a formatting format. I.e. we should use\n> >> warning(\"%s\", msg) in that case, not warning(msg).\n> >>\n> >> See 927dc330705 (advice.h: add missing __attribute__((format)) & fix\n> >> usage, 2021-07-13) for a similar bug/fix.\n> >\n> > Good point.\n> >\n> > Man, what a can of worms this all is.  Maybe I really should just drop\n> > patches 5, 6, and 8 for now...\n>\n> Yeah, I really think it's worth it to just sprinkle a tiny bit of\n> if/else (or a macro) here and print to stderr inline or not. We can make\n> some use of some usage.c when there's good reason to do so, but this bit\n> just seems like a needless digression.\n>\n> I hope all of this has helped somewhat ...\n\nAbsolutely; thanks for reviewing!  These parts may just end up in me\ndropping some patches for now (since they're not actually being used\nanyway), but I think it's all good feedback.\n"},{"id":"447623","messageId":"220203.86bkzn6ea8.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BF8VoQ7F7yvfzrpQEZwErxHzb9x8M_R9PrrM7vWzw=wSw@mail.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-03T17:15:18Z","receivedAt":"2022-02-03T17:39:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Feb 03 2022, Elijah Newren wrote:\n\n> On Thu, Feb 3, 2022 at 1:52 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>>\n>> On Thu, Feb 03 2022, Elijah Newren wrote:\n>>\n> [...]\n>> > ...also, even if we did require the `--write-tree` flag, we'd still\n>> > have to look at argc.  Since the option parsing handles both modes,\n>> > someone could leave off --write-tree, but include a bunch of other\n>> > options that only make sense with --write-tree.  Individually checking\n>> > the setting of every extra flag along with write_tree is a royal pain\n>> > and I don't want to repeat that for each new option added.  Simply\n>> > checking argc allows you to provide an error message if the user does\n>> > that.\n>> >\n>> > (And I think it's sad that in Git we often forgot to warn and notify\n>> > users of options that are only functional with certain other\n>> > arguments; it makes it harder for users to figure out, and has in the\n>> > past even made it harder for other developers to figure out what was\n>> > meant and how things are to be used.  I think I've seen multiple Git\n>> > devs be confused over ls-files --directory and --no-empty-directory\n>> > options, assuming they'd do something sensible for tracked files, when\n>> > in fact those arguments are simply ignored because they are only\n>> > modifiers for how untracked files are treated.)\n>>\n>> There's a much simpler way to do what you're trying to do here which is\n>> to only parse --write-tree, and as soon as you have that pass off two\n>> one function or the other, and have those functions call\n>> parse_options().\n>\n> But that makes --write-tree a mandatory argument when trying to use\n> that mode, right?  If so, that is not a simpler way to do what I'm\n> trying to do at all; it breaks my intended usage.\n>\n> --write-tree is a documentation-only construct that users should never\n> have to pass.\n>\n> Also, what happens if we remove the --trivial-merge flag and its whole\n> mode after a sufficient deprecation period?  Would the --write-tree\n> parameter remain required in your model to select the only existing\n> mode, simply due to us having gone through a transition period?\n\nYou can have your cake and eat it too by running parse_optionss() N\nnumber of times. Although perhaps in this case the end result isn't\nworth it.\n\nI was hoping this could be a simpler case of a subcommand dispatch, and\nperhaps it can still be generalized to that.\n\nIf the \"trivial\" mode never takes options and always 3 argv elements, we\ncould just run parse_options() for it with no options, after checking\nthat we have 3 arguments, and none start with '-'.\n\nBut the below is a generalization of this I tried out just now, it\npasses all your tests, and means that whenever you add new options you\ndon't need to keep saying \"no, not with the trivial mode\" for each one.\n\nBasically we run parse_options() once with the full set of options, and\nsave away argc/argv (note the lack of strvec_clear() there, that's a\nTODO memory leak).\n\nThen we've got a o.mode, which along with argc is the *only* thing we\npay attention to at that point.\n\nThen we dispatch to the \"trivial\" or \"write\" functions, which do\nparse_options() again, this time with only their options.\n\nIt means that e.g. this now works as expected:\n    \n    ./git merge-tree --trivial-merge -z origin/{master,next,seen}\n    error: unknown switch `z'\n    usage: git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\n    \n        --trivial-merge       do a trivial merge only\n\nI.e. we error out, with your verison we'll just ignore the -z.\n\nYour \"--trivial-merge is incompatible with all other options\" doesn't\nwork as you expect, and is buggy whether you want to go this route or\nnot, as the added tests show.\n\nBasically it does nothing at all. Because if you add --foo we'll die\nbefore we get there, parse_options() will die for us.\n\nBut if you do --trivial-merge -z your argc/argv will be trimmed, because\n-z is a known option. So your check is doing nothing. Your tests also\npass with this removal of the only option compatibily check on top:\n\t\n\tdiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n\tindex 58c0ddc5a32..08f18d43334 100644\n\t--- a/builtin/merge-tree.c\n\t+++ b/builtin/merge-tree.c\n\t@@ -476,7 +476,6 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n\t {\n\t \tstruct merge_tree_options o = { .show_messages = -1 };\n\t \tint expected_remaining_argc;\n\t-\tint original_argc;\n\t \n\t \tconst char * const merge_tree_usage[] = {\n\t \t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n\t@@ -505,7 +504,6 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n\t \t};\n\t \n\t \t/* Parse arguments */\n\t-\toriginal_argc = argc;\n\t \targc = parse_options(argc, argv, prefix, mt_options,\n\t \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n\t \tif (o.mode) {\n\t@@ -517,8 +515,6 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n\t \t\t\tusage_with_options(merge_tree_usage, mt_options);\n\t \t\to.mode = (argc == 2 ? 'w' : 't');\n\t \t}\n\t-\tif (o.mode == 't' && original_argc < argc)\n\t-\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n\t \n\t \t/* Do the relevant type of merge */\n\t \tif (o.mode == 'w')\n\nSo, here it is in all its glory :) A bit nasty for sure, but IMO\npreferrable to an ever expanding list of \"X isn't compatible with A\".\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 58c0ddc5a32..1d47912816d 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -12,6 +12,7 @@\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n #include \"quote.h\"\n+#include \"strvec.h\"\n \n static int line_termination = '\\n';\n \n@@ -371,13 +372,66 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(const char *base,\n-\t\t\t const char *branch1,\n-\t\t\t const char *branch2)\n+struct merge_tree_options {\n+\tint mode;\n+\tint allow_unrelated_histories;\n+\tint show_messages;\n+\tint exclude_modes_oids_stages;\n+};\n+\n+#define BUILTIN_MERGE_TREE_USAGE_WRITE \\\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\")\n+#define BUILTIN_MERGE_TREE_USAGE_TRIVIAL \\\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\")\n+\n+#define BUILTIN_MERGE_TREE_OPT_CMDMODE_TRIVIAL \\\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode, \\\n+\t\t\t    N_(\"do a trivial merge only\"), 't')\n+\n+#define BUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE \\\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode, \\\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"), \\\n+\t\t\t    'w')\n+\n+#define BUILTIN_MERGE_TREE_OPT_WRITE \\\n+\t\tBUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE, \\\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages, \\\n+\t\t\t N_(\"also show informational/conflict messages\")), \\\n+\t\tOPT_SET_INT('z', NULL, &line_termination, \\\n+\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'), \\\n+\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\", \\\n+\t\t\t   &o.exclude_modes_oids_stages, \\\n+\t\t\t   N_(\"list conflicted files without modes/oids/stages\"), \\\n+\t\t\t   PARSE_OPT_NONEG), \\\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\", \\\n+\t\t\t   &o.allow_unrelated_histories, \\\n+\t\t\t   N_(\"allow merging unrelated histories\"), \\\n+\t\t\t   PARSE_OPT_NONEG)\n+\n+static int trivial_merge(int argc, const char **argv, const char *prefix)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n+\tstruct merge_tree_options o = { 0 };\n+\tconst char * const usage[] = {\n+\t\tBUILTIN_MERGE_TREE_USAGE_TRIVIAL,\n+\t\tNULL,\n+\t};\n+\tstruct option options[] = {\n+\t\tBUILTIN_MERGE_TREE_OPT_CMDMODE_TRIVIAL,\n+\t\tOPT_END()\n+\t};\n+\tconst char *base, *branch1, *branch2;\n+\n+\targc = parse_options(argc, argv, prefix, options, usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (argc != 3)\n+\t\tBUG(\"should have ensured remaining argc == 3 already! Got %d\", argc);\n+\n+\tbase = argv[0];\n+\tbranch1 = argv[1];\n+\tbranch2 = argv[2];;\n \n \tbuf1 = get_tree_descriptor(r, t+0, base);\n \tbuf2 = get_tree_descriptor(r, t+1, branch1);\n@@ -391,24 +445,34 @@ static int trivial_merge(const char *base,\n \treturn 0;\n }\n \n-struct merge_tree_options {\n-\tint mode;\n-\tint allow_unrelated_histories;\n-\tint show_messages;\n-\tint exclude_modes_oids_stages;\n-};\n-\n-static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2,\n-\t\t      const char *prefix)\n+static int real_merge(int argc, const char **argv, const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *common;\n \tstruct commit_list *merge_bases = NULL;\n \tstruct commit_list *j;\n+\tstruct merge_tree_options o = { .show_messages = 1 };\n \tstruct merge_options opt;\n \tstruct merge_result result = { 0 };\n \n+\tconst char * const usage[] = {\n+\t\tBUILTIN_MERGE_TREE_USAGE_WRITE,\n+\t\tNULL,\n+\t};\n+\tstruct option options[] = {\n+\t\tBUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE,\n+\t\tBUILTIN_MERGE_TREE_OPT_WRITE,\n+\t\tOPT_END()\n+\t};\n+\tconst char *branch1, *branch2;\n+\n+\targc = parse_options(argc, argv, prefix, options, usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (argc != 2)\n+\t\tBUG(\"should have ensured remaining argc == 2 already! Got %d\", argc);\n+\tbranch1 = argv[0];\n+\tbranch2 = argv[1];\n+\n \tparent1 = get_merge_parent(branch1);\n \tif (!parent1)\n \t\thelp_unknown_ref(branch1, \"merge-tree\",\n@@ -431,7 +495,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common && !o->allow_unrelated_histories)\n+\tif (!common && !o.allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tfor (j = common; j; j = j->next)\n \t\tcommit_list_insert(j->item, &merge_bases);\n@@ -440,8 +504,8 @@ static int real_merge(struct merge_tree_options *o,\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n \n-\tif (o->show_messages == -1)\n-\t\to->show_messages = !result.clean;\n+\tif (o.show_messages == -1)\n+\t\to.show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n \tif (!result.clean) {\n@@ -453,7 +517,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n \t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n-\t\t\tif (!o->exclude_modes_oids_stages)\n+\t\t\tif (!o.exclude_modes_oids_stages)\n \t\t\t\tprintf(\"%06o %s %d\\t\",\n \t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n \t\t\telse if (last && !strcmp(last, name))\n@@ -464,7 +528,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\t}\n \t\tstring_list_clear(&conflicted_files, 1);\n \t}\n-\tif (o->show_messages) {\n+\tif (o.show_messages) {\n \t\tputchar(line_termination);\n \t\tmerge_display_update_messages(&opt, &result, stdout);\n \t}\n@@ -474,40 +538,32 @@ static int real_merge(struct merge_tree_options *o,\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { .show_messages = -1 };\n+\tstruct merge_tree_options o;\n \tint expected_remaining_argc;\n-\tint original_argc;\n-\n+\tint original_argc = argc;\n+\tstruct strvec original_args = STRVEC_INIT;\n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n-\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tBUILTIN_MERGE_TREE_USAGE_WRITE,\n+\t\tBUILTIN_MERGE_TREE_USAGE_TRIVIAL,\n \t\tNULL\n \t};\n \tstruct option mt_options[] = {\n-\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n-\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n-\t\t\t    'w'),\n-\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n-\t\t\t    N_(\"do a trivial merge only\"), 't'),\n-\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n-\t\t\t N_(\"also show informational/conflict messages\")),\n-\t\tOPT_SET_INT('z', NULL, &line_termination,\n-\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n-\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n-\t\t\t   &o.exclude_modes_oids_stages,\n-\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n-\t\t\t   PARSE_OPT_NONEG),\n-\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n-\t\t\t   &o.allow_unrelated_histories,\n-\t\t\t   N_(\"allow merging unrelated histories\"),\n-\t\t\t   PARSE_OPT_NONEG),\n+\t\tBUILTIN_MERGE_TREE_OPT_CMDMODE_TRIVIAL,\n+\t\tBUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE,\n+\t\tBUILTIN_MERGE_TREE_OPT_WRITE,\n \t\tOPT_END()\n \t};\n \n+\t/* We only care about deciding \"o.mode\" here */\n+\to.mode = 0;\n+\t/*\n+\t * We need our original argv, and\n+\t * PARSE_OPT_KEEP_{ARGV0,UNKNOWN} would do the wrong thing\n+\t */\n+\tstrvec_pushv(&original_args, argv);\n \t/* Parse arguments */\n-\toriginal_argc = argc;\n \targc = parse_options(argc, argv, prefix, mt_options,\n-\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\t\t\t     merge_tree_usage, 0);\n \tif (o.mode) {\n \t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n \t\tif (argc != expected_remaining_argc)\n@@ -517,12 +573,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\tusage_with_options(merge_tree_usage, mt_options);\n \t\to.mode = (argc == 2 ? 'w' : 't');\n \t}\n-\tif (o.mode == 't' && original_argc < argc)\n-\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == 'w')\n-\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n+\t\treturn real_merge(original_argc, original_args.v, prefix);\n \telse\n-\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n+\t\treturn trivial_merge(original_argc, original_args.v, prefix);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 4de089d976d..749bdb6862d 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -92,6 +92,18 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+for opt in $(git merge-tree --git-completion-helper-all)\n+do\n+\tif test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n+\tthen\n+\t\tcontinue\n+\tfi\n+\n+\ttest_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n+\t\ttest_expect_code 129 git merge-tree --trivial-merge $opt side1 side2 side3\n+\t'\n+done\n+\n test_expect_success 'test conflict notices and such' '\n \ttest_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n"},{"id":"447633","messageId":"CABPp-BEvQWbcwZw68P6d9Ud+AqrEnTrjXb4Tne5DMaCtfu_Tsg@mail.gmail.com","threadId":"57288","inReplyTo":"220203.86bkzn6ea8.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-03T18:18:16Z","receivedAt":"2022-02-03T18:18:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 3, 2022 at 9:38 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n>\n> On Thu, Feb 03 2022, Elijah Newren wrote:\n>\n[...]\n> > But that makes --write-tree a mandatory argument when trying to use\n> > that mode, right?  If so, that is not a simpler way to do what I'm\n> > trying to do at all; it breaks my intended usage.\n> >\n> > --write-tree is a documentation-only construct that users should never\n> > have to pass.\n> >\n> > Also, what happens if we remove the --trivial-merge flag and its whole\n> > mode after a sufficient deprecation period?  Would the --write-tree\n> > parameter remain required in your model to select the only existing\n> > mode, simply due to us having gone through a transition period?\n>\n> You can have your cake and eat it too by running parse_optionss() N\n> number of times. Although perhaps in this case the end result isn't\n> worth it.\n>\n> I was hoping this could be a simpler case of a subcommand dispatch, and\n> perhaps it can still be generalized to that.\n>\n> If the \"trivial\" mode never takes options and always 3 argv elements, we\n> could just run parse_options() for it with no options, after checking\n> that we have 3 arguments, and none start with '-'.\n>\n> But the below is a generalization of this I tried out just now, it\n> passes all your tests, and means that whenever you add new options you\n> don't need to keep saying \"no, not with the trivial mode\" for each one.\n>\n> Basically we run parse_options() once with the full set of options, and\n> save away argc/argv (note the lack of strvec_clear() there, that's a\n> TODO memory leak).\n>\n> Then we've got a o.mode, which along with argc is the *only* thing we\n> pay attention to at that point.\n>\n> Then we dispatch to the \"trivial\" or \"write\" functions, which do\n> parse_options() again, this time with only their options.\n>\n> It means that e.g. this now works as expected:\n>\n>     ./git merge-tree --trivial-merge -z origin/{master,next,seen}\n>     error: unknown switch `z'\n>     usage: git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\n>\n>         --trivial-merge       do a trivial merge only\n>\n> I.e. we error out, with your verison we'll just ignore the -z.\n>\n> Your \"--trivial-merge is incompatible with all other options\" doesn't\n> work as you expect, and is buggy whether you want to go this route or\n> not, as the added tests show.\n>\n> Basically it does nothing at all. Because if you add --foo we'll die\n> before we get there, parse_options() will die for us.\n>\n> But if you do --trivial-merge -z your argc/argv will be trimmed, because\n> -z is a known option. So your check is doing nothing.\n\nGah, good catch.  How did I get the check reversed??  I know I tested\nit at one point.  Anyway, this fixes it:\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 58c0ddc5a3..fb25e3d10d 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -505,19 +505,22 @@ int cmd_merge_tree(int argc, const char **argv, const cha\nr *prefix)\n        };\n\n        /* Parse arguments */\n-       original_argc = argc;\n+       original_argc = argc - 1; /* ignoring program name, i.e. argv[0] */\n        argc = parse_options(argc, argv, prefix, mt_options,\n                             merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n        if (o.mode) {\n                expected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n                if (argc != expected_remaining_argc)\n                        usage_with_options(merge_tree_usage, mt_options);\n+               if (o.mode == 't')\n+                       /* Removal of `--trivial-merge` is expected */\n+                       original_argc_options--;\n        } else {\n                if (argc < 2 || argc > 3)\n                        usage_with_options(merge_tree_usage, mt_options);\n                o.mode = (argc == 2 ? 'w' : 't');\n        }\n-       if (o.mode == 't' && original_argc < argc)\n+       if (o.mode == 't' && argc < original_argc)\n                die(_(\"--trivial-merge is incompatible with all other\noptions\"));\n\n        /* Do the relevant type of merge */\n\n\nand results in this error being shown when any other option is listed\nwith --trivial-merge.\n\n> So, here it is in all its glory :) A bit nasty for sure, but IMO\n> preferrable to an ever expanding list of \"X isn't compatible with A\".\n\nAgreed...on both counts.  :-)\n\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 58c0ddc5a32..1d47912816d 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -12,6 +12,7 @@\n>  #include \"exec-cmd.h\"\n>  #include \"merge-blobs.h\"\n>  #include \"quote.h\"\n> +#include \"strvec.h\"\n>\n>  static int line_termination = '\\n';\n>\n> @@ -371,13 +372,66 @@ static void *get_tree_descriptor(struct repository *r,\n>         return buf;\n>  }\n>\n> -static int trivial_merge(const char *base,\n> -                        const char *branch1,\n> -                        const char *branch2)\n> +struct merge_tree_options {\n> +       int mode;\n> +       int allow_unrelated_histories;\n> +       int show_messages;\n> +       int exclude_modes_oids_stages;\n> +};\n> +\n> +#define BUILTIN_MERGE_TREE_USAGE_WRITE \\\n> +               N_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\")\n> +#define BUILTIN_MERGE_TREE_USAGE_TRIVIAL \\\n> +               N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\")\n> +\n> +#define BUILTIN_MERGE_TREE_OPT_CMDMODE_TRIVIAL \\\n> +               OPT_CMDMODE(0, \"trivial-merge\", &o.mode, \\\n> +                           N_(\"do a trivial merge only\"), 't')\n> +\n> +#define BUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE \\\n> +               OPT_CMDMODE(0, \"write-tree\", &o.mode, \\\n> +                           N_(\"do a real merge instead of a trivial merge\"), \\\n> +                           'w')\n> +\n> +#define BUILTIN_MERGE_TREE_OPT_WRITE \\\n> +               BUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE, \\\n> +               OPT_BOOL(0, \"messages\", &o.show_messages, \\\n> +                        N_(\"also show informational/conflict messages\")), \\\n> +               OPT_SET_INT('z', NULL, &line_termination, \\\n> +                           N_(\"separate paths with the NUL character\"), '\\0'), \\\n> +               OPT_BOOL_F('l', \"exclude-modes-oids-stages\", \\\n> +                          &o.exclude_modes_oids_stages, \\\n> +                          N_(\"list conflicted files without modes/oids/stages\"), \\\n> +                          PARSE_OPT_NONEG), \\\n> +               OPT_BOOL_F(0, \"allow-unrelated-histories\", \\\n> +                          &o.allow_unrelated_histories, \\\n> +                          N_(\"allow merging unrelated histories\"), \\\n> +                          PARSE_OPT_NONEG)\n> +\n> +static int trivial_merge(int argc, const char **argv, const char *prefix)\n>  {\n>         struct repository *r = the_repository;\n>         struct tree_desc t[3];\n>         void *buf1, *buf2, *buf3;\n> +       struct merge_tree_options o = { 0 };\n> +       const char * const usage[] = {\n> +               BUILTIN_MERGE_TREE_USAGE_TRIVIAL,\n> +               NULL,\n> +       };\n> +       struct option options[] = {\n> +               BUILTIN_MERGE_TREE_OPT_CMDMODE_TRIVIAL,\n> +               OPT_END()\n> +       };\n> +       const char *base, *branch1, *branch2;\n> +\n> +       argc = parse_options(argc, argv, prefix, options, usage,\n> +                            PARSE_OPT_STOP_AT_NON_OPTION);\n> +       if (argc != 3)\n> +               BUG(\"should have ensured remaining argc == 3 already! Got %d\", argc);\n> +\n> +       base = argv[0];\n> +       branch1 = argv[1];\n> +       branch2 = argv[2];;\n>\n>         buf1 = get_tree_descriptor(r, t+0, base);\n>         buf2 = get_tree_descriptor(r, t+1, branch1);\n> @@ -391,24 +445,34 @@ static int trivial_merge(const char *base,\n>         return 0;\n>  }\n>\n> -struct merge_tree_options {\n> -       int mode;\n> -       int allow_unrelated_histories;\n> -       int show_messages;\n> -       int exclude_modes_oids_stages;\n> -};\n> -\n> -static int real_merge(struct merge_tree_options *o,\n> -                     const char *branch1, const char *branch2,\n> -                     const char *prefix)\n> +static int real_merge(int argc, const char **argv, const char *prefix)\n>  {\n>         struct commit *parent1, *parent2;\n>         struct commit_list *common;\n>         struct commit_list *merge_bases = NULL;\n>         struct commit_list *j;\n> +       struct merge_tree_options o = { .show_messages = 1 };\n>         struct merge_options opt;\n>         struct merge_result result = { 0 };\n>\n> +       const char * const usage[] = {\n> +               BUILTIN_MERGE_TREE_USAGE_WRITE,\n> +               NULL,\n> +       };\n> +       struct option options[] = {\n> +               BUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE,\n> +               BUILTIN_MERGE_TREE_OPT_WRITE,\n> +               OPT_END()\n> +       };\n> +       const char *branch1, *branch2;\n> +\n> +       argc = parse_options(argc, argv, prefix, options, usage,\n> +                            PARSE_OPT_STOP_AT_NON_OPTION);\n> +       if (argc != 2)\n> +               BUG(\"should have ensured remaining argc == 2 already! Got %d\", argc);\n> +       branch1 = argv[0];\n> +       branch2 = argv[1];\n> +\n>         parent1 = get_merge_parent(branch1);\n>         if (!parent1)\n>                 help_unknown_ref(branch1, \"merge-tree\",\n> @@ -431,7 +495,7 @@ static int real_merge(struct merge_tree_options *o,\n>          * merge_incore_recursive in merge-ort.h\n>          */\n>         common = get_merge_bases(parent1, parent2);\n> -       if (!common && !o->allow_unrelated_histories)\n> +       if (!common && !o.allow_unrelated_histories)\n>                 die(_(\"refusing to merge unrelated histories\"));\n>         for (j = common; j; j = j->next)\n>                 commit_list_insert(j->item, &merge_bases);\n> @@ -440,8 +504,8 @@ static int real_merge(struct merge_tree_options *o,\n>         if (result.clean < 0)\n>                 die(_(\"failure to merge\"));\n>\n> -       if (o->show_messages == -1)\n> -               o->show_messages = !result.clean;\n> +       if (o.show_messages == -1)\n> +               o.show_messages = !result.clean;\n>\n>         puts(oid_to_hex(&result.tree->object.oid));\n>         if (!result.clean) {\n> @@ -453,7 +517,7 @@ static int real_merge(struct merge_tree_options *o,\n>                 for (i = 0; i < conflicted_files.nr; i++) {\n>                         const char *name = conflicted_files.items[i].string;\n>                         struct stage_info *c = conflicted_files.items[i].util;\n> -                       if (!o->exclude_modes_oids_stages)\n> +                       if (!o.exclude_modes_oids_stages)\n>                                 printf(\"%06o %s %d\\t\",\n>                                        c->mode, oid_to_hex(&c->oid), c->stage);\n>                         else if (last && !strcmp(last, name))\n> @@ -464,7 +528,7 @@ static int real_merge(struct merge_tree_options *o,\n>                 }\n>                 string_list_clear(&conflicted_files, 1);\n>         }\n> -       if (o->show_messages) {\n> +       if (o.show_messages) {\n>                 putchar(line_termination);\n>                 merge_display_update_messages(&opt, &result, stdout);\n>         }\n> @@ -474,40 +538,32 @@ static int real_merge(struct merge_tree_options *o,\n>\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -       struct merge_tree_options o = { .show_messages = -1 };\n> +       struct merge_tree_options o;\n>         int expected_remaining_argc;\n> -       int original_argc;\n> -\n> +       int original_argc = argc;\n> +       struct strvec original_args = STRVEC_INIT;\n>         const char * const merge_tree_usage[] = {\n> -               N_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n> -               N_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> +               BUILTIN_MERGE_TREE_USAGE_WRITE,\n> +               BUILTIN_MERGE_TREE_USAGE_TRIVIAL,\n>                 NULL\n>         };\n>         struct option mt_options[] = {\n> -               OPT_CMDMODE(0, \"write-tree\", &o.mode,\n> -                           N_(\"do a real merge instead of a trivial merge\"),\n> -                           'w'),\n> -               OPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n> -                           N_(\"do a trivial merge only\"), 't'),\n> -               OPT_BOOL(0, \"messages\", &o.show_messages,\n> -                        N_(\"also show informational/conflict messages\")),\n> -               OPT_SET_INT('z', NULL, &line_termination,\n> -                           N_(\"separate paths with the NUL character\"), '\\0'),\n> -               OPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n> -                          &o.exclude_modes_oids_stages,\n> -                          N_(\"list conflicted files without modes/oids/stages\"),\n> -                          PARSE_OPT_NONEG),\n> -               OPT_BOOL_F(0, \"allow-unrelated-histories\",\n> -                          &o.allow_unrelated_histories,\n> -                          N_(\"allow merging unrelated histories\"),\n> -                          PARSE_OPT_NONEG),\n> +               BUILTIN_MERGE_TREE_OPT_CMDMODE_TRIVIAL,\n> +               BUILTIN_MERGE_TREE_OPT_CMDMODE_WRITE,\n> +               BUILTIN_MERGE_TREE_OPT_WRITE,\n>                 OPT_END()\n>         };\n>\n> +       /* We only care about deciding \"o.mode\" here */\n> +       o.mode = 0;\n> +       /*\n> +        * We need our original argv, and\n> +        * PARSE_OPT_KEEP_{ARGV0,UNKNOWN} would do the wrong thing\n> +        */\n> +       strvec_pushv(&original_args, argv);\n>         /* Parse arguments */\n> -       original_argc = argc;\n>         argc = parse_options(argc, argv, prefix, mt_options,\n> -                            merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> +                            merge_tree_usage, 0);\n>         if (o.mode) {\n>                 expected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n>                 if (argc != expected_remaining_argc)\n> @@ -517,12 +573,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>                         usage_with_options(merge_tree_usage, mt_options);\n>                 o.mode = (argc == 2 ? 'w' : 't');\n>         }\n> -       if (o.mode == 't' && original_argc < argc)\n> -               die(_(\"--trivial-merge is incompatible with all other options\"));\n>\n>         /* Do the relevant type of merge */\n>         if (o.mode == 'w')\n> -               return real_merge(&o, argv[0], argv[1], prefix);\n> +               return real_merge(original_argc, original_args.v, prefix);\n>         else\n> -               return trivial_merge(argv[0], argv[1], argv[2]);\n> +               return trivial_merge(original_argc, original_args.v, prefix);\n>  }\n\n\nYeah, that's waaay more complex than my code which basically just\nneeds 6 lines to check the incompatibility; the basic patch for that\npart (going back to before this patch we're commenting on) was just:\n\n +    original_argc = argc - 1;  /* ignore program name, i.e. argv[0] */\n...\n +        if (o.mode == 't')\n +            /* Removal of `--trivial-merge` is expected */\n +            original_argc_options--;\n...\n +    if (o.mode == 't' && argc < original_argc)\n +        die(_(\"--trivial-merge is incompatible with all other options\"));\n\n\n> diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\n> index 4de089d976d..749bdb6862d 100755\n> --- a/t/t4301-merge-tree-write-tree.sh\n> +++ b/t/t4301-merge-tree-write-tree.sh\n> @@ -92,6 +92,18 @@ test_expect_success 'Barf on too many arguments' '\n>         grep \"^usage: git merge-tree\" expect\n>  '\n>\n> +for opt in $(git merge-tree --git-completion-helper-all)\n> +do\n> +       if test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n> +       then\n> +               continue\n> +       fi\n> +\n> +       test_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n> +               test_expect_code 129 git merge-tree --trivial-merge $opt side1 side2 side3\n> +       '\n> +done\n\nYep, I was clearly missing an important testcase.  Thanks for\nproviding one and for pointing out the error in my patch!  Very cool.\n"},{"id":"447649","messageId":"xmqqee4jsokt.fsf@gitster.g","threadId":"57288","inReplyTo":"20220203104241.yvfragan6ucecfjl@gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-03T20:05:38Z","receivedAt":"2022-02-03T20:05:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Altmanninger <aclopte@gmail.com> writes:\n\n> I wonder how the UI of a tool that does non-worktree/non-index cherry-picks\n> will look like.  I'd expect it to produce the same output as merge-tree,\n> except cherry-pick should probably output a commit OID, not a tree.\n\nA tool that creates a \"merge\", \"cherry-pick\" and \"revert\" should all\noutput a resulting commit object name, not a tree object.\n\nAnd \"merge-tree\" that creates a three-way merge to apply a change to\ngo from tree A to tree B on top of tree X should take three tree-ish\nobject names and produce a resulting one tree object name.\n\nUsing the \"merge-tree\" that merges two trees using the third tree as\na common ancestor, these three higher-level tools can perform\n\"merge\", \"cherry-pick\", and \"revert\".  They would all take two\ncommit objects and produce one commit object.\n\n\n\n   \n"},{"id":"447686","messageId":"xmqqmtj7pksd.fsf@gitster.g","threadId":"57288","inReplyTo":"243134dc2478e21f67a6d9cb999d6754b616f6ee.1643479633.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/13] merge-tree: provide a list of which files have conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-03T23:55:46Z","receivedAt":"2022-02-03T23:55:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +Conflicted file list\n> +~~~~~~~~~~~~~~~~~~~~\n> +\n> +This is a sequence of lines containing a filename on each line, quoted\n> +as explained for the configuration variable `core.quotePath` (see\n> +linkgit:git-config[1]).\n\nMakes sense.  Ideally things like this should be discoverable by\ninspecting the tree object shown as the result of the (conflicted)\nmerge, but since the design of the output is to show only a single\ntree, there is nowhere to store such an extra piece of information\nper path (grepping for markers in blobs of course does not count).\n\nI guess an alternative to show four trees when conflicted instead of\none (i.e. the primary tree may either contain only the cleanly\nmerged paths _or_ also blobs with conflict markers for conflicted\npaths; the three other trees record three stages that would be in\nthe index, if we were performing the same merge using the index),\nbut a machine-parseable list of paths is fine.\n\n> +\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n> +\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n> +\t\t\tconst char *name = conflicted_files.items[i].string;\n> +\t\t\tif (last && !strcmp(last, name))\n> +\t\t\t\tcontinue;\n> +\t\t\twrite_name_quoted_relative(\n> +\t\t\t\tname, prefix, stdout, line_termination);\n> +\t\t\tlast = name;\n\nOK.  The iteration used here makes casual readers wonder why the\nhelper doesn't make paths unique, but the string list item holds\nin its util pointer a pointer to a structure with <stage, mode, oid>\ntuple, so it is natural to make the consumer, who wants uniquified\nlist, responsible for deduping, like this loop.\n\n> +\t\t}\n> +\t\tstring_list_clear(&conflicted_files, 1);\n\nAnd the stage-info structure associated with these paths are\ndeallocated with this call.  Good.\n\n> +\t}\n\n"},{"id":"447705","messageId":"YfywEBIIJWLPM2kr@google.com","threadId":"57288","inReplyTo":"02c29f920d0d5fde6d85f7b86a69be92e3f0f34d.1643787281.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2022-02-04T04:48:16Z","receivedAt":"2022-02-04T04:48:27Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2022.02.02 07:34, Elijah Newren via GitGitGadget wrote:\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index 58731c19422..569485815a0 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -3,26 +3,73 @@ git-merge-tree(1)\n>  \n>  NAME\n>  ----\n> -git-merge-tree - Show three-way merge without touching index\n> +git-merge-tree - Perform merge without touching index or working tree\n>  \n>  \n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git merge-tree' <base-tree> <branch1> <branch2>\n> +'git merge-tree' [--write-tree] <branch1> <branch2>\n> +'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n>  \n>  DESCRIPTION\n>  -----------\n> -Reads three tree-ish, and output trivial merge results and\n> -conflicting stages to the standard output.  This is similar to\n> -what three-way 'git read-tree -m' does, but instead of storing the\n> -results in the index, the command outputs the entries to the\n> -standard output.\n> -\n> -This is meant to be used by higher level scripts to compute\n> -merge results outside of the index, and stuff the results back into the\n> -index.  For this reason, the output from the command omits\n> -entries that match the <branch1> tree.\n> +\n> +Performs a merge, but does not make any new commits and does not read\n> +from or write to either the working tree or index.\n> +\n> +The second form is deprecated and supported only for backward\n> +compatibility.  It will likely be removed in the future, and will not\n> +be discussed further in this manual.\n> +\n> +The first form will merge the two branches, doing a real merge.  A real\n> +merge is distinguished from a trivial merge in that it includes:\n> +\n> +  * three way content merges of individual files\n> +  * rename detection\n> +  * proper directory/file conflict handling\n> +  * recursive ancestor consolidation (i.e. when there is more than one\n> +    merge base, creating a virtual merge base by merging the merge bases)\n> +  * etc.\n> +\n> +After the merge completes, it will create a new toplevel tree object.\n> +See `OUTPUT` below for details.\n> +\n> +OUTPUT\n> +------\n> +\n> +For either a successful or conflicted merge, the output from\n> +git-merge-tree is simply one line:\n> +\n> +\t<OID of toplevel tree>\n> +\n> +The printed tree object corresponds to what would be checked out in\n> +the working tree at the end of `git merge`, and thus may have files\n> +with conflict markers in them.\n> +\n> +EXIT STATUS\n> +-----------\n> +\n> +For a successful, non-conflicted merge, the exit status is 0.  When the\n> +merge has conflicts, the exit status is 1.  If the merge is not able to\n> +complete (or start) due to some kind of error, the exit status is\n> +something other than 0 or 1.\n> +\n> +USAGE NOTES\n> +-----------\n> +\n> +git-merge-tree was written to be low-level plumbing, similar to\n> +hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n> +be used as a part of a series of steps such as\n> +\n> +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> +       test $? -eq 0 || die \"There were conflicts...\"\n> +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n> +       git update-ref $BRANCH1 $NEWCOMMIT\n> +\n> +However, it does not quite fit into the same category of low-level\n> +plumbing commands since the possibility of merge conflicts give it a\n> +much higher chance of the command not succeeding.\n\nI found this final paragraph confusing. It seems to be hinting at some\nconclusion it expects readers to make, but I haven't been able to figure\nout what. Could this be made more explicit, or perhaps dropped\naltogether?\n"},{"id":"447708","messageId":"CABPp-BEVXa9rP7J_hNrWxOziWGAZ6YpP302dLOvNbAVumth3JQ@mail.gmail.com","threadId":"57288","inReplyTo":"YfywEBIIJWLPM2kr@google.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-04T06:08:07Z","receivedAt":"2022-02-04T06:08:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Feb 3, 2022 at 8:48 PM Josh Steadmon <steadmon@google.com> wrote:\n>\n> On 2022.02.02 07:34, Elijah Newren via GitGitGadget wrote:\n[...]\n> > +USAGE NOTES\n> > +-----------\n> > +\n> > +git-merge-tree was written to be low-level plumbing, similar to\n> > +hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n> > +be used as a part of a series of steps such as\n> > +\n> > +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> > +       test $? -eq 0 || die \"There were conflicts...\"\n> > +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n> > +       git update-ref $BRANCH1 $NEWCOMMIT\n> > +\n> > +However, it does not quite fit into the same category of low-level\n> > +plumbing commands since the possibility of merge conflicts give it a\n> > +much higher chance of the command not succeeding.\n>\n> I found this final paragraph confusing. It seems to be hinting at some\n> conclusion it expects readers to make, but I haven't been able to figure\n> out what. Could this be made more explicit, or perhaps dropped\n> altogether?\n\nYep, I'll drop it.\n"},{"id":"447783","messageId":"nycvar.QRO.7.76.6.2202050009220.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BERtRDeyF3MhOQhAFwjoykOKwXoz6635NK7j2SEKp1b3A@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-04T23:10:53Z","receivedAt":"2022-02-04T23:11:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 29 Jan 2022, Elijah Newren wrote:\n\n> On Sat, Jan 29, 2022 at 12:23 AM Johannes Sixt <j6t@kdbg.org> wrote:\n> >\n> > Just a heckling from the peanut gallery...\n> >\n> > Am 29.01.22 um 07:08 schrieb Elijah Newren:\n> > > On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n> > > <Johannes.Schindelin@gmx.de> wrote:\n> > >> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n> > >> missing from the second conflict, in the output we would see stages 1, 2,\n> > >> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n> > >> different conflicts.\n> > >\n> > > I don't understand why you're fixating on the stage here.  Why would\n> > > you want to group all the stage 2s together, count them up, and then\n> > > determine there are N conflicting files because there are N stage 2's?\n> >\n> > Looks like you are misunderstanding Dscho's point: When you have two\n> > conflicts, the first with stages 1 and 2, the second with stages 2 and\n> > 3, then the 2s occur lumped together when the 4 lines are printed in a\n> > row, and that is the cue to the parser where the new conflict begins.\n> > Dscho did not mean that all N 2s of should be listed together.\n>\n> Ah, so...I didn't understand his misunderstanding?  Using stages as a\n> cue to the parser where the new conflict begins is broken; you should\n> instead check for when the filename listed on a line does not match\n> the filename on the previous line.\n\nBut that would break down in case of rename/rename conflicts, right?\n\n> In particular, if one conflict has stages 1 and 2, and the next conflict\n> has only stage 3, then looking at stages only might cause you to\n> accidentally lump unrelated conflicts together.\n\nPrecisely. That's why I would love to have a way to deviate from the\noutput of `ls-files -u`'s format, and have a reliable way to indicate\nstages that belong to the same merge conflict.\n\nThanks,\nDscho\n"},{"id":"447784","messageId":"nycvar.QRO.7.76.6.2202050011350.347@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BEn=fvmTyYEzjSfvKkYyHj0te=6ck6WF+Jor+L1jKrVkg@mail.gmail.com","subject":"Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-04T23:12:39Z","receivedAt":"2022-02-04T23:12:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Fri, 28 Jan 2022, Elijah Newren wrote:\n\n> On Fri, Jan 28, 2022 at 8:57 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > Hi Elijah,\n> >\n> > On Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n> >\n> > > From: Elijah Newren <newren@gmail.com>\n> > >\n> [...]\n> > > diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> > > index fd7a867de60..041a4ac2785 100644\n> > > --- a/Documentation/git-merge-tree.txt\n> > > +++ b/Documentation/git-merge-tree.txt\n> > > @@ -58,6 +58,7 @@ simply one line:\n> > >  Whereas for a conflicted merge, the output is by default of the form:\n> > >\n> > >       <OID of toplevel tree>\n> > > +     <Conflicted file list>\n> > >       <Informational messages>\n> >\n> > To distinguish between the list of conflicted files and the informational\n> > messages, I think it would be good to insert an empty line, as a\n> > separator, like.\n>\n> Yes, I agree; that's why I did so.  :-)\n\nMy concern was that I did not see this empty line reflected in the quoted\ndiff. I would have expected an empty line between the `<Conflicted [...]>`\nand the `<Informational [...]>` line.\n\nThanks,\nDscho\n"},{"id":"447810","messageId":"CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202050009220.347@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-05T00:54:43Z","receivedAt":"2022-02-05T00:55:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Feb 4, 2022 at 3:10 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Sat, 29 Jan 2022, Elijah Newren wrote:\n>\n> > On Sat, Jan 29, 2022 at 12:23 AM Johannes Sixt <j6t@kdbg.org> wrote:\n> > >\n> > > Just a heckling from the peanut gallery...\n> > >\n> > > Am 29.01.22 um 07:08 schrieb Elijah Newren:\n> > > > On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n> > > > <Johannes.Schindelin@gmx.de> wrote:\n> > > >> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n> > > >> missing from the second conflict, in the output we would see stages 1, 2,\n> > > >> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n> > > >> different conflicts.\n> > > >\n> > > > I don't understand why you're fixating on the stage here.  Why would\n> > > > you want to group all the stage 2s together, count them up, and then\n> > > > determine there are N conflicting files because there are N stage 2's?\n> > >\n> > > Looks like you are misunderstanding Dscho's point: When you have two\n> > > conflicts, the first with stages 1 and 2, the second with stages 2 and\n> > > 3, then the 2s occur lumped together when the 4 lines are printed in a\n> > > row, and that is the cue to the parser where the new conflict begins.\n> > > Dscho did not mean that all N 2s of should be listed together.\n> >\n> > Ah, so...I didn't understand his misunderstanding?  Using stages as a\n> > cue to the parser where the new conflict begins is broken; you should\n> > instead check for when the filename listed on a line does not match\n> > the filename on the previous line.\n>\n> But that would break down in case of rename/rename conflicts, right?\n>\n> > In particular, if one conflict has stages 1 and 2, and the next conflict\n> > has only stage 3, then looking at stages only might cause you to\n> > accidentally lump unrelated conflicts together.\n>\n> Precisely. That's why I would love to have a way to deviate from the\n> output of `ls-files -u`'s format, and have a reliable way to indicate\n> stages that belong to the same merge conflict.\n\nAh, attempting to somehow identify and present logical separate\nconflicts?  That could be awesome, but I'm not sure it's technically\npossible.  It certainly isn't with today's merge-ort.\n\nLet me ask some questions first...\n\nIf I understand you correctly then in the event of a rename/rename,\ni.e. foo->bar & foo->baz, then you want foo's, bar's, & baz's stages\nall listed together.  Right?  And in some way that you can identify\nthem as related?\n\nIf we do so, how do we mark the beginning and the end of what you call\n\"the same merge conflict\"?  If you say it's always 3 stages (with the\npossibility of all-zero modes/oids), then what about the rename/rename\ncase above modified so that the side that did foo->baz also added a\ndifferent 'bar'?  That'd be 4 non-zero modes/oids, all of them\nrelevant.  Or what if the side that did foo->bar also renamed\nsomething else to 'baz', giving us even more non-zero stages for these\nthree paths?  Perhaps you consider these different conflicts and want\nthem listed separately -- if so, where does one conflict begin and\nanother start and which stages are parts of which conflict?\n\nIf you are attempting to somehow present the stuff that \"belongs to\nthe same merge conflict\" are you also trying to identify what kind of\nmerge conflict it is?  If so, do you want each type of merge conflict\nlisted?  For example, let's switch from the example above of logically\ndisjoint paths coming together to result in more than 3 stages, and\ninstead pick an example with a single logical path with less than\nthree stages.  And now let's say that path has multiple conflicts\nassociated with it; let's use an example with 3: rename/delete +\nmodify/delete + directory/file (one side renames foo->bar while\nmodifying the contents, the other side deletes foo and adds the\ndirectory 'bar/').  In this case, there is a target file 'bar' that\nhas two non-zero modes/oids in the ls-files-u output.  If all three\ntypes of conflicts need to be listed, does each need to be listed with\nthe two non-zero modes/oids (and perhaps one zero mode/oid), resulting\nin six listings for 'bar'?  Or would the duplication be confusing\nenough that we instead decide to list some merge conflicts with no\nstages associated with them?\n\nThinking about both sets of questions in the last two paragraphs from\na higher level -- should we focus on and group the higher order stages\nby the individual conflicts that happen, or should we group them by\nthe paths that they happen to (which is what `ls-files -u` happens to\ndo), or should we not bother grouping them and instead duplicate the\nhigher order stages for each logical conflict it is part of?\n\nAs an alternative to duplicating higher order stages, do we sometimes\ndecide to \"lump\" separate conflicts together and treat them as one\nconflict?  If so, what are the rules on how we decide to lump\nconflicts and when not to?  Is there a bright line boundary?  And can\nit be done without sucking in arbitrarily more stages for a single\nconflict?\n\n\nSome testcases that might be useful while considering the above\nquestions: take a look at the \"rad\", \"rrdd\", and \"mod6\" tests of\nt6422.  How many \"same merge conflicts\" are there for each of those,\nand what's the boundary between them?  And can you give the answer in\nthe form of rules that generically handle all cases, rather than just\nanswering these three specific cases?\n\n\nI've thought about this problem long and hard before (in part because\nof some conversations I had with Edward Thompson about libgit2 and\nmerging at Git Merge 2020).  It wasn't at all clear to me that libgit2\nhad considered anything beyond simple rename cases.  The only rules I\never figured out that made sense to me was \"group the stages by target\nfilename rather than by logical conflict\" (so we get `ls -files -u`\npopulated) and print a meant-for-human message for each logical\nconflict (found in the <Informational Messages> section for\nmerge-tree), and make NO attempt to connect stages by conflict type.\n\nI'm sure that's not what you wanted to hear, and maybe doesn't even\nplay nicely with your design.  But short of ignoring the edge and\ncorner cases, I don't see how to solve that problem.  If you do just\nwant to ignore edge and corner cases, then just ignore the\nrename/rename case you brought up in the first place and just use\n`ls-files -u`-type output as-is within your design.  If you don't want\nto ignore edge cases and want something that works with a specific\ndesign that somehow groups conflicted file stages by conflict type,\nthen we're going to have to dig into all these questions above and do\nsome big replumbing within merge-ort.\n"},{"id":"447918","messageId":"YgGgCasl1eVz679E@google.com","threadId":"57288","inReplyTo":"63f42df21aec5bda50e4414493eb59dcb64e5558.1643787281.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2022-02-07T22:41:13Z","receivedAt":"2022-02-07T22:41:25Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"On Wed, Feb 02, 2022 at 07:34:29AM +0000, Elijah Newren via GitGitGadget wrote:\n> \n> \n> Let merge-tree accept a `--write-tree` parameter for choosing real\n> merges instead of trivial merges, and accept an optional\n> `--trivial-merge` option to get the traditional behavior.  Note that\n> these accept different numbers of arguments, though, so these names\n> need not actually be used.\n> \n> Note that real merges differ from trivial merges in that they handle:\n>   - three way content merges\n>   - recursive ancestor consolidation\n>   - renames\n>   - proper directory/file conflict handling\n>   - etc.\n> Basically all the stuff you'd expect from `git merge`, just without\n> updating the index and working tree.  The initial shell added here does\n> nothing more than die with \"real merges are not yet implemented\", but\n> that will be fixed in subsequent commits.\n> \n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  builtin/merge-tree.c | 61 +++++++++++++++++++++++++++++++++++++-------\n>  git.c                |  2 +-\n>  2 files changed, 53 insertions(+), 10 deletions(-)\n> \n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 914ec960b7e..e98ec8a9f1d 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -3,13 +3,12 @@\n>  #include \"tree-walk.h\"\n>  #include \"xdiff-interface.h\"\n>  #include \"object-store.h\"\n> +#include \"parse-options.h\"\n>  #include \"repository.h\"\n>  #include \"blob.h\"\n>  #include \"exec-cmd.h\"\n>  #include \"merge-blobs.h\"\n>  \n> -static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n> -\n>  struct merge_list {\n>  \tstruct merge_list *next;\n>  \tstruct merge_list *link;\t/* other stages for this object */\n> @@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n>  \treturn buf;\n>  }\n>  \n> -static int trivial_merge(int argc, const char **argv)\n> +static int trivial_merge(const char *base,\n> +\t\t\t const char *branch1,\n> +\t\t\t const char *branch2)\n>  {\n>  \tstruct repository *r = the_repository;\n>  \tstruct tree_desc t[3];\n>  \tvoid *buf1, *buf2, *buf3;\n>  \n> -\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n> -\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n> -\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n> +\tbuf1 = get_tree_descriptor(r, t+0, base);\n> +\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n> +\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n>  \ttrivial_merge_trees(t, \"\");\n>  \tfree(buf1);\n>  \tfree(buf2);\n> @@ -384,9 +385,51 @@ static int trivial_merge(int argc, const char **argv)\n>  \treturn 0;\n>  }\n>  \n> +struct merge_tree_options {\n> +\tint mode;\n> +};\n> +\n> +static int real_merge(struct merge_tree_options *o,\n> +\t\t      const char *branch1, const char *branch2)\n> +{\n> +\tdie(_(\"real merges are not yet implemented\"));\n> +}\n> +\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>  {\n> -\tif (argc != 4)\n> -\t\tusage(merge_tree_usage);\n> -\treturn trivial_merge(argc, argv);\n> +\tstruct merge_tree_options o = { 0 };\n> +\tint expected_remaining_argc;\n> +\n> +\tconst char * const merge_tree_usage[] = {\n> +\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n> +\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n> +\t\tNULL\n> +\t};\n> +\tstruct option mt_options[] = {\n> +\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n> +\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n> +\t\t\t    'w'),\n> +\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n> +\t\t\t    N_(\"do a trivial merge only\"), 't'),\n> +\t\tOPT_END()\n> +\t};\n\nIn the review club last week I had mentioned I thought OPT_CMDMODE\nworked well with enums. I found some a reasonably nice example in\nbuiltin/replace.c:cmd_replace(), although I have some Opinions about the\nenum declaration placement there. Regardless, I think using an enum\ninstead of a single character would make this more readable - otherwise\nI need to remember what 'w' means when I'm reasoning about how many args\nto expect below.\n\n\n> +\n> +\t/* Parse arguments */\n> +\targc = parse_options(argc, argv, prefix, mt_options,\n> +\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n> +\tif (o.mode) {\n> +\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n> +\t\tif (argc != expected_remaining_argc)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t} else {\n> +\t\tif (argc < 2 || argc > 3)\n> +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n> +\t\to.mode = (argc == 2 ? 'w' : 't');\n> +\t}\n> +\n> +\t/* Do the relevant type of merge */\n> +\tif (o.mode == 'w')\n> +\t\treturn real_merge(&o, argv[0], argv[1]);\n> +\telse\n> +\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n>  }\n\nSorry for the slow reply.\n\n - Emily\n"},{"id":"447932","messageId":"xmqq7da6gsfk.fsf@gitster.g","threadId":"57288","inReplyTo":"YgGgCasl1eVz679E@google.com","subject":"Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-07T23:36:47Z","receivedAt":"2022-02-08T01:07:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Emily Shaffer <emilyshaffer@google.com> writes:\n\n>> +\tconst char * const merge_tree_usage[] = {\n>> +\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n>> +\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n>> +\t\tNULL\n>> +\t};\n>> +\tstruct option mt_options[] = {\n>> +\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n>> +\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n>> +\t\t\t    'w'),\n>> +\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n>> +\t\t\t    N_(\"do a trivial merge only\"), 't'),\n>> +\t\tOPT_END()\n>> +\t};\n>\n> In the review club last week I had mentioned I thought OPT_CMDMODE\n> worked well with enums. I found some a reasonably nice example in\n> builtin/replace.c:cmd_replace(), although I have some Opinions about the\n> enum declaration placement there. Regardless, I think using an enum\n> instead of a single character would make this more readable - otherwise\n> I need to remember what 'w' means when I'm reasoning about how many args\n> to expect below.\n\nI am reasonably sure whoever did the above use of OPT_CMDMODE()\nfeature mimicked an existing one that use it to make options with a\nsingle-letter shorthand mutually exclusive.  If the options are with\nshort-hand, you wouldn't be complaining that you do not know what\n'w' stands for.  When using it with options without short-hand, like\nthis case, I'd agree it would make it easier to read to use symbolic\nconstants of some kind.\n\n"},{"id":"448307","messageId":"4a7cd5542bb2f89b4874e4115542ccee9c4639af.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 01/12] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:42Z","receivedAt":"2022-02-12T20:34:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"448308","messageId":"4780ff6784d426bf0a96859ef9bf9c14e87d5f50.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 02/12] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:43Z","receivedAt":"2022-02-12T20:35:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"448309","messageId":"60253745f5c59ac4c75d46df1f98ee722523f166.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:44Z","receivedAt":"2022-02-12T20:35:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 84 +++++++++++++++++++++++++++++++++++++++-----\n git.c                |  2 +-\n 2 files changed, 76 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..0f9d928e862 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,74 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+enum mode {\n+\tMODE_UNKNOWN,\n+\tMODE_TRIVIAL,\n+\tMODE_REAL,\n+};\n+\n+struct merge_tree_options {\n+\tint mode;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n+\t\t\t    MODE_REAL),\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n+\t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tswitch (o.mode) {\n+\tdefault:\n+\t\tBUG(\"unexpected command mode %d\", o.mode);\n+\tcase MODE_UNKNOWN:\n+\t\tswitch (argc) {\n+\t\tdefault:\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\tcase 2:\n+\t\t\to.mode = MODE_REAL;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\to.mode = MODE_TRIVIAL;\n+\t\t\tbreak;\n+\t\t}\n+\t\texpected_remaining_argc = argc;\n+\t\tbreak;\n+\tcase MODE_REAL:\n+\t\texpected_remaining_argc = 2;\n+\t\tbreak;\n+\tcase MODE_TRIVIAL:\n+\t\texpected_remaining_argc = 3;\n+\t\tbreak;\n+\t}\n+\n+\tif (argc != expected_remaining_argc)\n+\t\tusage_with_options(merge_tree_usage, mt_options);\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.mode == MODE_REAL)\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..6090a1289db 100644\n--- a/git.c\n+++ b/git.c\n@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"448310","messageId":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v3.git.1643787281.gitgitgadget@gmail.com","subject":"[PATCH v4 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:41Z","receivedAt":"2022-02-12T20:35:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Note: Depends on en/remerge-diff (to avoid a small textual conflict)\n\n== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs. It could\nbe used to do merges when in a bare repository (thus potentially making it\nof interest to Git hosting sites, i.e. \"Server side merges\"), or for doing a\nmerge of branches that aren't checked out.\n\nIt does not handle similar functionality for cherry-picks, rebases, or\nreverts; that is also of interest, but is being deferred for a future\nseries.\n\n== Quick Overview ==\n\n * Patches 1-2: preparatory cleanups\n * Patches 3-4: implement basic real merges\n * Patches 5-6: include informational messages (\"CONFLICT\" messages and\n   such) in output\n * Patches 7-10: add ability to include ls-files -u style of info in the\n   output\n * Patch 11: support --allow-unrelated-histories\n * Patch 12: augment the manual with potential usage mistakes\n\n== Updates Log ==\n\nMany thanks to the many reviewers who provided good feedback on the most\nrecent round -- Junio, Ævar, Josh, Emily, and perhaps some others I've\nforgotten from review club.\n\nUpdates since v3 (or v5, if you include the rounds at\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/):\n\n * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n   being used and opened a can of worms[1]\n * [Patch 3] Restructured argument checking, including using an enum\n * [Patch 4] Restored the extended paragraph about the deprecated form of\n   git-merge-tree, mentioned write-tree in plumbing commands, and a few\n   other small fixups to the documentation\n * [Patch 4] Also provide an example of a clean merge rather than just a\n   conflicted one\n * [Patch 6] Fix the incompatible arguments check and add some tests for it\n * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n   easier to read (less repeated sed)\n * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n   option for now\n * [Patch 10] When -z passed, the tree in the first section should have a\n   trailing NUL rather than trailing newline [1]\n   https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n\nStuff NOT included that reviewers brought up in earlier rounds:\n\n * Very generic (mode, oid, stage, filename) printing formatting[2]\n * Always printing 3 stages for each filename with conflicts[3]\n * Attempting to group conflict stages by logical conflict rather than by\n   affected target filepath[4]\n * Providing similar functionality for doing cherry-picks/rebases/reverts,\n   i.e. a scheme for three-way merges with a specified merge-base[5]. That's\n   being deferred to a future series. [2]\n   https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n   [3]\n   https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n   [4]\n   https://lore.kernel.org/git/CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com/\n   [5]\n   https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n\nUpdates since v2:\n\n * Improved patches from Dscho for the diff_warn_rename_limit() handling\n * Add a -z option for NUL-terminated conflict info lines (so that filenames\n   do not have to be quoted)\n\nUpdates since v1 (or v3 depending on how you count; thanks to René, Ævar,\nChristian, Dscho for very helpful feedback):\n\n * New patch from Dscho allowing diff_warn_rename_limit() to print somewhere\n   other than stdout (I hope he's okay with me including his Signed-off-by)\n * Now prints filenames relative to prefix, much like ls-files\n * Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave\n   it a -l shorthand; I'm wondering if I should just drop this option,\n   though.\n * And numerous cleanups, in lots of areas:\n   * Multiple parse-options cleanups\n   * Lots of commit message cleanups\n   * Wording tweaks to the \"Description\" section of the manual\n   * Several small code cleanups\n * I dropped the RFC label\n\nUpdates since original submission v2 (thanks to Christian, Dscho, Ramsay,\nand René for suggestions and comments):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since original submission v1 (thanks to Johannes Altmanninger and\nFabian for suggestions):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\nElijah Newren (12):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-tree: allow `ls-files -u` style info to be NUL terminated\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\n Documentation/git-merge-tree.txt | 204 ++++++++++++++++++++++++--\n builtin/merge-tree.c             | 189 ++++++++++++++++++++++--\n git.c                            |   2 +-\n merge-ort.c                      | 109 +++++++++-----\n merge-ort.h                      |  29 ++++\n t/t4301-merge-tree-write-tree.sh | 238 +++++++++++++++++++++++++++++++\n 6 files changed, 709 insertions(+), 62 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\n\nbase-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n\nRange-diff vs v3:\n\n  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()\n  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function\n  3:  63f42df21ae !  3:  60253745f5c merge-tree: add option parsing and initial shell for real merge function\n     @@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)\n       \treturn 0;\n       }\n       \n     ++enum mode {\n     ++\tMODE_UNKNOWN,\n     ++\tMODE_TRIVIAL,\n     ++\tMODE_REAL,\n     ++};\n     ++\n      +struct merge_tree_options {\n      +\tint mode;\n      +};\n     @@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)\n      +\tstruct option mt_options[] = {\n      +\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n      +\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n     -+\t\t\t    'w'),\n     ++\t\t\t    MODE_REAL),\n      +\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n     -+\t\t\t    N_(\"do a trivial merge only\"), 't'),\n     ++\t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n      +\t\tOPT_END()\n      +\t};\n      +\n      +\t/* Parse arguments */\n      +\targc = parse_options(argc, argv, prefix, mt_options,\n      +\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n     -+\tif (o.mode) {\n     -+\t\texpected_remaining_argc = (o.mode == 'w' ? 2 : 3);\n     -+\t\tif (argc != expected_remaining_argc)\n     ++\tswitch (o.mode) {\n     ++\tdefault:\n     ++\t\tBUG(\"unexpected command mode %d\", o.mode);\n     ++\tcase MODE_UNKNOWN:\n     ++\t\tswitch (argc) {\n     ++\t\tdefault:\n      +\t\t\tusage_with_options(merge_tree_usage, mt_options);\n     -+\t} else {\n     -+\t\tif (argc < 2 || argc > 3)\n     -+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n     -+\t\to.mode = (argc == 2 ? 'w' : 't');\n     ++\t\tcase 2:\n     ++\t\t\to.mode = MODE_REAL;\n     ++\t\t\tbreak;\n     ++\t\tcase 3:\n     ++\t\t\to.mode = MODE_TRIVIAL;\n     ++\t\t\tbreak;\n     ++\t\t}\n     ++\t\texpected_remaining_argc = argc;\n     ++\t\tbreak;\n     ++\tcase MODE_REAL:\n     ++\t\texpected_remaining_argc = 2;\n     ++\t\tbreak;\n     ++\tcase MODE_TRIVIAL:\n     ++\t\texpected_remaining_argc = 3;\n     ++\t\tbreak;\n      +\t}\n      +\n     ++\tif (argc != expected_remaining_argc)\n     ++\t\tusage_with_options(merge_tree_usage, mt_options);\n     ++\n      +\t/* Do the relevant type of merge */\n     -+\tif (o.mode == 'w')\n     ++\tif (o.mode == MODE_REAL)\n      +\t\treturn real_merge(&o, argv[0], argv[1]);\n      +\telse\n      +\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n  4:  02c29f920d0 !  4:  d7b51da94e6 merge-tree: implement real merges\n     @@ Commit message\n          conflict/warning messages normally output during a merge, or have quick\n          access to a list of files with conflicts.  That is not available in this\n          preliminary implementation, but subsequent commits will add that\n     -    ability.\n     +    ability (meaning that NEWTREE would be a lot more than a tree in the\n     +    case of conflicts).\n      \n          This also marks the traditional trivial merge of merge-tree as\n          deprecated.  The trivial merge not only had limited applicability, the\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +Performs a merge, but does not make any new commits and does not read\n      +from or write to either the working tree or index.\n      +\n     -+The second form is deprecated and supported only for backward\n     -+compatibility.  It will likely be removed in the future, and will not\n     -+be discussed further in this manual.\n     -+\n      +The first form will merge the two branches, doing a real merge.  A real\n      +merge is distinguished from a trivial merge in that it includes:\n      +\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +    merge base, creating a virtual merge base by merging the merge bases)\n      +  * etc.\n      +\n     -+After the merge completes, it will create a new toplevel tree object.\n     -+See `OUTPUT` below for details.\n     ++After the merge completes, the first form will create a new toplevel\n     ++tree object.  See `OUTPUT` below for details.\n     ++\n     ++The second form is deprecated; it is kept for backward compatibility\n     ++reasons but may be deleted in the future.  Other than the optional\n     ++`--trivial-merge`, it accepts no options.  It can only do a trivial\n     ++merge.  It reads three tree-ish, and outputs trivial merge results and\n     ++conflicting stages to the standard output in a semi-diff format.\n     ++Since this was designed for higher level scripts to consume and merge\n     ++the results back into the index, it omits entries that match\n     ++<branch1>.  The result of this second form is is similar to what\n     ++three-way 'git read-tree -m' does, but instead of storing the results\n     ++in the index, the command outputs the entries to the standard output.\n     ++This form not only has limited applicability, the output format is\n     ++also difficult to work with, and it will generally be less performant\n     ++than the first form even on successful merges (especially if working\n     ++in large repositories).  The remainder of this manual will only\n     ++discuss the first form.\n      +\n      +OUTPUT\n      +------\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +For a successful, non-conflicted merge, the exit status is 0.  When the\n      +merge has conflicts, the exit status is 1.  If the merge is not able to\n      +complete (or start) due to some kind of error, the exit status is\n     -+something other than 0 or 1.\n     ++something other than 0 or 1 (and the output is unspecified).\n      +\n      +USAGE NOTES\n      +-----------\n      +\n      +git-merge-tree was written to be low-level plumbing, similar to\n     -+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could\n     -+be used as a part of a series of steps such as\n     ++hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n     ++Thus, it could be used as a part of a series of steps such as\n      +\n      +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n      +       test $? -eq 0 || die \"There were conflicts...\"\n      +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n      +       git update-ref $BRANCH1 $NEWCOMMIT\n     -+\n     -+However, it does not quite fit into the same category of low-level\n     -+plumbing commands since the possibility of merge conflicts give it a\n     -+much higher chance of the command not succeeding.\n       \n       GIT\n       ---\n     @@ t/t4301-merge-tree-write-tree.sh (new)\n      +\n      +\tgit branch side1 &&\n      +\tgit branch side2 &&\n     ++\tgit branch side3 &&\n      +\n      +\tgit checkout side1 &&\n      +\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n     @@ t/t4301-merge-tree-write-tree.sh (new)\n      +\t>whatever/empty &&\n      +\tgit add numbers greeting whatever/empty &&\n      +\ttest_tick &&\n     -+\tgit commit -m other-modifications\n     ++\tgit commit -m other-modifications &&\n     ++\n     ++\tgit checkout side3 &&\n     ++\tgit mv numbers sequence &&\n     ++\ttest_tick &&\n     ++\tgit commit -m rename-numbers\n     ++'\n     ++\n     ++test_expect_success 'Clean merge' '\n     ++\tgit merge-tree --write-tree side1 side3 >RESULT &&\n     ++\tq_to_tab <<-EOF >expect &&\n     ++\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n     ++\t100644 blob $(git rev-parse side1:numbers)Qsequence\n     ++\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n     ++\tEOF\n     ++\n     ++\tgit ls-tree $(cat RESULT) >actual &&\n     ++\ttest_cmp expect actual\n      +'\n      +\n      +test_expect_success 'Content merge and a few conflicts' '\n     @@ t/t4301-merge-tree-write-tree.sh (new)\n      +'\n      +\n      +test_expect_success 'Barf on too many arguments' '\n     -+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&\n     ++\ttest_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&\n      +\n      +\tgrep \"^usage: git merge-tree\" expect\n      +'\n  5:  290b42846b5 <  -:  ----------- Introduce a variant of the `warning()` function that takes a `FILE *`\n  6:  2083fbe9b2e <  -:  ----------- diff: allow diff_warn_rename_limit to write somewhere besides stderr\n  7:  1be858e6aa6 !  5:  58a5594aeb6 merge-ort: split out a separate display_update_messages() function\n     @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op\n      +\n      +\t/* Also include needed rename limit adjustment now */\n      +\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     -+\t\t\t       opti->renames.needed_limit, 0, stderr);\n     ++\t\t\t       opti->renames.needed_limit, 0);\n      +\n      +\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n      +}\n     @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op\n       \t\t\t    struct tree *head,\n       \t\t\t    struct merge_result *result,\n      @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,\n     + \t\tfclose(fp);\n       \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n       \t}\n     - \n     +-\n      -\tif (display_update_msgs) {\n      -\t\tstruct merge_options_internal *opti = result->priv;\n      -\t\tstruct hashmap_iter iter;\n     @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,\n      -\n      -\t\t/* Also include needed rename limit adjustment now */\n      -\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n     --\t\t\t\t       opti->renames.needed_limit, 0, stderr);\n     +-\t\t\t\t       opti->renames.needed_limit, 0);\n      -\n      -\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n      -\t}\n  8:  04c3bdc44d2 <  -:  ----------- merge-ort: allow update messages to be written to different file stream\n  9:  c8ed002408d !  6:  fa55cb4d644 merge-tree: support including merge messages in output\n     @@ Documentation/git-merge-tree.txt: git-merge-tree - Perform merge without touchin\n       'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n       \n       DESCRIPTION\n     -@@ Documentation/git-merge-tree.txt: merge is distinguished from a trivial merge in that it includes:\n     - After the merge completes, it will create a new toplevel tree object.\n     - See `OUTPUT` below for details.\n     +@@ Documentation/git-merge-tree.txt: than the first form even on successful merges (especially if working\n     + in large repositories).  The remainder of this manual will only\n     + discuss the first form.\n       \n      +OPTIONS\n      +-------\n     @@ Documentation/git-merge-tree.txt: merge is distinguished from a trivial merge in\n      +\t<Informational messages>\n      +\n      +These are discussed individually below.\n     -+\n     + \n     +-The printed tree object corresponds to what would be checked out in\n     +-the working tree at the end of `git merge`, and thus may have files\n     +-with conflict markers in them.\n      +OID of toplevel tree\n      +~~~~~~~~~~~~~~~~~~~~\n      +\n     @@ Documentation/git-merge-tree.txt: merge is distinguished from a trivial merge in\n      +\n      +This always starts with a blank line to separate it from the previous\n      +section, and then has free-form messages about the merge, such as:\n     - \n     --The printed tree object corresponds to what would be checked out in\n     --the working tree at the end of `git merge`, and thus may have files\n     --with conflict markers in them.\n     ++\n      +  * \"Auto-merging <file>\"\n      +  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n      +  * \"Failed to merge submodule <submodule> (<reason>)\"\n     @@ Documentation/git-merge-tree.txt: merge is distinguished from a trivial merge in\n       \n       EXIT STATUS\n       -----------\n     -@@ Documentation/git-merge-tree.txt: be used as a part of a series of steps such as\n     - \n     - However, it does not quite fit into the same category of low-level\n     - plumbing commands since the possibility of merge conflicts give it a\n     --much higher chance of the command not succeeding.\n     -+much higher chance of the command not succeeding (and NEWTREE containing\n     -+a bunch of stuff other than just a toplevel tree).\n     +@@ Documentation/git-merge-tree.txt: Thus, it could be used as a part of a series of steps such as\n     +        NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n     +        git update-ref $BRANCH1 $NEWCOMMIT\n       \n     ++Note that when the exit status is non-zero, NEWTREE in this sequence\n     ++will contain a lot more output than just a tree.\n     ++\n       GIT\n       ---\n     + Part of the linkgit:git[1] suite\n      \n       ## builtin/merge-tree.c ##\n     -@@ builtin/merge-tree.c: static int trivial_merge(const char *base,\n     +@@ builtin/merge-tree.c: enum mode {\n       \n       struct merge_tree_options {\n       \tint mode;\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \tputs(oid_to_hex(&result.tree->object.oid));\n      +\tif (o->show_messages) {\n      +\t\tprintf(\"\\n\");\n     -+\t\tmerge_display_update_messages(&opt, &result, stdout);\n     ++\t\tmerge_display_update_messages(&opt, &result);\n      +\t}\n       \tmerge_finalize(&opt, &result);\n       \treturn !result.clean; /* result.clean < 0 handled above */\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\tNULL\n       \t};\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t    'w'),\n     + \t\t\t    MODE_REAL),\n       \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n     - \t\t\t    N_(\"do a trivial merge only\"), 't'),\n     + \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n      +\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n      +\t\t\t N_(\"also show informational/conflict messages\")),\n       \t\tOPT_END()\n       \t};\n       \n       \t/* Parse arguments */\n     -+\toriginal_argc = argc;\n     ++\toriginal_argc = argc - 1; /* ignoring argv[0] */\n       \targc = parse_options(argc, argv, prefix, mt_options,\n       \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n     - \tif (o.mode) {\n     + \tswitch (o.mode) {\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\tusage_with_options(merge_tree_usage, mt_options);\n     - \t\to.mode = (argc == 2 ? 'w' : 't');\n     + \t\tbreak;\n     + \tcase MODE_TRIVIAL:\n     + \t\texpected_remaining_argc = 3;\n     ++\t\t/* Removal of `--trivial-merge` is expected */\n     ++\t\toriginal_argc--;\n     + \t\tbreak;\n       \t}\n     -+\tif (o.mode == 't' && original_argc < argc)\n     ++\tif (o.mode == MODE_TRIVIAL && argc < original_argc)\n      +\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n       \n     - \t/* Do the relevant type of merge */\n     - \tif (o.mode == 'w')\n     + \tif (argc != expected_remaining_argc)\n     + \t\tusage_with_options(merge_tree_usage, mt_options);\n      \n       ## t/t4301-merge-tree-write-tree.sh ##\n      @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many arguments' '\n       \tgrep \"^usage: git merge-tree\" expect\n       '\n       \n     ++anonymize_hash() {\n     ++\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" \"$@\"\n     ++}\n     ++\n      +test_expect_success 'test conflict notices and such' '\n      +\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n     -+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n     ++\tanonymize_hash out >actual &&\n      +\n      +\t# Expected results:\n      +\t#   \"greeting\" should merge with conflicts\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many argument\n      +\n      +\ttest_cmp expect actual\n      +'\n     ++\n     ++for opt in $(git merge-tree --git-completion-helper-all)\n     ++do\n     ++\tif test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n     ++\tthen\n     ++\t\tcontinue\n     ++\tfi\n     ++\n     ++\ttest_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n     ++\t\ttest_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3\n     ++\t'\n     ++done\n      +\n       test_done\n 10:  1c2a3f5ef63 !  7:  f3ad7add515 merge-ort: provide a merge_get_conflicted_files() helper function\n     @@ merge-ort.h\n       \n       struct commit;\n       struct tree;\n     -@@ merge-ort.h: void merge_display_update_messages(struct merge_options *opt,\n     - \t\t\t\t   struct merge_result *result,\n     - \t\t\t\t   FILE *stream);\n     +@@ merge-ort.h: void merge_switch_to_result(struct merge_options *opt,\n     + void merge_display_update_messages(struct merge_options *opt,\n     + \t\t\t\t   struct merge_result *result);\n       \n      +struct stage_info {\n      +\tstruct object_id oid;\n 11:  9c2389eef0e !  8:  6058190d1b1 merge-tree: provide a list of which files have conflicts\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n      +\t}\n       \tif (o->show_messages) {\n       \t\tprintf(\"\\n\");\n     - \t\tmerge_display_update_messages(&opt, &result, stdout);\n     + \t\tmerge_display_update_messages(&opt, &result);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n       \n       \t/* Do the relevant type of merge */\n     - \tif (o.mode == 'w')\n     + \tif (o.mode == MODE_REAL)\n      -\t\treturn real_merge(&o, argv[0], argv[1]);\n      +\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n       \telse\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and\n       \n       \tAuto-merging greeting\n       \tCONFLICT (content): Merge conflict in greeting\n     -@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '\n     - \ttest_cmp expect actual\n     - '\n     +@@ t/t4301-merge-tree-write-tree.sh: do\n     + \t'\n     + done\n       \n      +test_expect_success 'Just the conflicted files without the messages' '\n      +\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n     -+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n     ++\tanonymize_hash out >actual &&\n      +\n      +\ttest_write_lines HASH greeting whatever~side1 >expect &&\n      +\n 12:  2188a8ca1e7 !  9:  435f66ea699 merge-tree: provide easy access to `ls-files -u` style info\n     @@ Commit message\n          Much like `git merge` updates the index with information of the form\n              (mode, oid, stage, name)\n          provide this output for conflicted files for merge-tree as well.\n     -    Provide an --exclude-modes-oids-stages/-l option for users to exclude\n     -    the mode, oid, and stage and only get the list of conflicted filenames.\n     +    Provide a --name-only option for users to exclude the mode, oid, and\n     +    stage and only get the list of conflicted filenames.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: See `OUTPUT` below for details.\n     +@@ Documentation/git-merge-tree.txt: discuss the first form.\n       OPTIONS\n       -------\n       \n     -+--exclude-oids-and-modes::\n     -+\tInstead of writing a list of (mode, oid, stage, path) tuples\n     -+\tto output for conflicted files, just provide a list of\n     -+\tfilenames with conflicts.\n     ++--name-only::\n     ++\tIn the Conflicted file info section, instead of writing a list\n     ++\tof (mode, oid, stage, path) tuples to output for conflicted\n     ++\tfiles, just provide a list of filenames with conflicts (and\n     ++\tdo not list filenames multiple times if they have multiple\n     ++\tconflicting stages).\n      +\n       --[no-]messages::\n       \tWrite any informational messages such as \"Auto-merging <path>\"\n     @@ Documentation/git-merge-tree.txt: This is a tree object that represents what wou\n      +\n      +The filename will be quoted as explained for the configuration\n      +variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n     -+the `--exclude-oids-and-modes` option is passed, the mode, object, and\n     -+stage will be omitted.\n     ++the `--name-only` option is passed, the mode, object, and stage will\n     ++be omitted.\n       \n       Informational messages\n       ~~~~~~~~~~~~~~~~~~~~~~\n     @@ Documentation/git-merge-tree.txt: This is a tree object that represents what wou\n       \n         * \"Auto-merging <file>\"\n         * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n     -@@ Documentation/git-merge-tree.txt: plumbing commands since the possibility of merge conflicts give it a\n     - much higher chance of the command not succeeding (and NEWTREE containing\n     - a bunch of stuff other than just a toplevel tree).\n     +@@ Documentation/git-merge-tree.txt: Thus, it could be used as a part of a series of steps such as\n     + Note that when the exit status is non-zero, NEWTREE in this sequence\n     + will contain a lot more output than just a tree.\n       \n      +git-merge-tree was written to provide users with the same information\n      +that they'd have access to if using `git merge`:\n     @@ Documentation/git-merge-tree.txt: plumbing commands since the possibility of mer\n       Part of the linkgit:git[1] suite\n      \n       ## builtin/merge-tree.c ##\n     -@@ builtin/merge-tree.c: static int trivial_merge(const char *base,\n     +@@ builtin/merge-tree.c: enum mode {\n       struct merge_tree_options {\n       \tint mode;\n       \tint show_messages;\n     -+\tint exclude_modes_oids_stages;\n     ++\tint name_only;\n       };\n       \n       static int real_merge(struct merge_tree_options *o,\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\t\tconst char *name = conflicted_files.items[i].string;\n      -\t\t\tif (last && !strcmp(last, name))\n      +\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n     -+\t\t\tif (!o->exclude_modes_oids_stages)\n     ++\t\t\tif (!o->name_only)\n      +\t\t\t\tprintf(\"%06o %s %d\\t\",\n      +\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n      +\t\t\telse if (last && !strcmp(last, name))\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\t\twrite_name_quoted_relative(\n       \t\t\t\tname, prefix, stdout, line_termination);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t    N_(\"do a trivial merge only\"), 't'),\n     + \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n       \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n       \t\t\t N_(\"also show informational/conflict messages\")),\n     -+\t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n     -+\t\t\t   &o.exclude_modes_oids_stages,\n     -+\t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n     ++\t\tOPT_BOOL_F(0, \"name-only\",\n     ++\t\t\t   &o.name_only,\n     ++\t\t\t   N_(\"list filenames without modes/oids/stages\"),\n      +\t\t\t   PARSE_OPT_NONEG),\n       \t\tOPT_END()\n       \t};\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Content merge and a few c\n       \ttest_when_finished \"git reset --hard\" &&\n       \n       \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n     -@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many arguments' '\n     - '\n     +@@ t/t4301-merge-tree-write-tree.sh: anonymize_hash() {\n     + }\n       \n       test_expect_success 'test conflict notices and such' '\n      -\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n     -+\ttest_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&\n     - \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n     ++\ttest_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out &&\n     + \tanonymize_hash out >actual &&\n       \n       \t# Expected results:\n     -@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '\n     - '\n     +@@ t/t4301-merge-tree-write-tree.sh: do\n     + done\n       \n       test_expect_success 'Just the conflicted files without the messages' '\n      -\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n     -+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&\n     - \tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n     ++\ttest_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out &&\n     + \tanonymize_hash out >actual &&\n       \n       \ttest_write_lines HASH greeting whatever~side1 >expect &&\n      @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Just the conflicted files without the messages' '\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Just the conflicted files\n       \n      +test_expect_success 'Check conflicted oids and modes without messages' '\n      +\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n     -+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n     ++\tanonymize_hash out >actual &&\n      +\n      +\t# Compare the basic output format\n      +\tq_to_tab >expect <<-\\EOF &&\n 13:  52339b396fa ! 10:  5f253e298b3 merge-tree: allow `ls-files -u` style info to be NUL terminated\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: See `OUTPUT` below for details.\n     +@@ Documentation/git-merge-tree.txt: discuss the first form.\n       OPTIONS\n       -------\n       \n     @@ Documentation/git-merge-tree.txt: See `OUTPUT` below for details.\n      +\tnewline.  Also begin the messages section with a NUL character\n      +\tinstead of a newline.  See OUTPUT below for more information.\n      +\n     - --exclude-oids-and-modes::\n     - \tInstead of writing a list of (mode, oid, stage, path) tuples\n     - \tto output for conflicted files, just provide a list of\n     + --name-only::\n     + \tIn the Conflicted file info section, instead of writing a list\n     + \tof (mode, oid, stage, path) tuples to output for conflicted\n      @@ Documentation/git-merge-tree.txt: OID of toplevel tree\n       \n       This is a tree object that represents what would be checked out in the\n       working tree at the end of `git merge`.  If there were conflicts, then\n      -files within this tree may have embedded conflict markers.\n      +files within this tree may have embedded conflict markers.  This section\n     -+is always followed by a newline.\n     ++is always followed by a newline (or NUL if `-z` is passed).\n       \n       Conflicted file info\n       ~~~~~~~~~~~~~~~~~~~~\n      @@ Documentation/git-merge-tree.txt: This is a sequence of lines with the format\n       The filename will be quoted as explained for the configuration\n       variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n     - the `--exclude-oids-and-modes` option is passed, the mode, object, and\n     --stage will be omitted.\n     -+stage will be omitted.  If `-z` is passed, the \"lines\" are terminated\n     -+by a NUL character instead of a newline character.\n     + the `--name-only` option is passed, the mode, object, and stage will\n     +-be omitted.\n     ++be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n     ++character instead of a newline character.\n       \n       Informational messages\n       ~~~~~~~~~~~~~~~~~~~~~~\n     @@ Documentation/git-merge-tree.txt: This is a sequence of lines with the format\n       \n      \n       ## builtin/merge-tree.c ##\n     +@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n     + \tif (o->show_messages == -1)\n     + \t\to->show_messages = !result.clean;\n     + \n     +-\tputs(oid_to_hex(&result.tree->object.oid));\n     ++\tprintf(\"%s%c\", oid_to_hex(&result.tree->object.oid), line_termination);\n     + \tif (!result.clean) {\n     + \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n     + \t\tconst char *last = NULL;\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\tstring_list_clear(&conflicted_files, 1);\n       \t}\n       \tif (o->show_messages) {\n      -\t\tprintf(\"\\n\");\n      +\t\tputchar(line_termination);\n     - \t\tmerge_display_update_messages(&opt, &result, stdout);\n     + \t\tmerge_display_update_messages(&opt, &result);\n       \t}\n       \tmerge_finalize(&opt, &result);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t    N_(\"do a trivial merge only\"), 't'),\n     + \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n       \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n       \t\t\t N_(\"also show informational/conflict messages\")),\n      +\t\tOPT_SET_INT('z', NULL, &line_termination,\n      +\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n     - \t\tOPT_BOOL_F('l', \"exclude-modes-oids-stages\",\n     - \t\t\t   &o.exclude_modes_oids_stages,\n     - \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n     + \t\tOPT_BOOL_F(0, \"name-only\",\n     + \t\t\t   &o.name_only,\n     + \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n      \n       ## t/t4301-merge-tree-write-tree.sh ##\n      @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and modes without messages' '\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n      +\tgit commit -m \"Renamed numbers\" &&\n      +\n      +\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n     -+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" out >actual &&\n     ++\tanonymize_hash out >actual &&\n      +\n      +\t# Expected results:\n      +\t#   \"greeting\" should merge with conflicts\n      +\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n      +\t#   \"Αυτά μου φαίνονται κινέζικα\" should have a conflict\n     -+\techo HASH >expect &&\n     ++\techo HASH | lf_to_nul >expect &&\n      +\n      +\tq_to_tab <<-EOF | lf_to_nul >>expect &&\n      +\t100644 HASH 1Qgreeting\n 14:  c854ecb5f4a ! 11:  e706cf31c6e merge-tree: add a --allow-unrelated-histories flag\n     @@ Documentation/git-merge-tree.txt: OPTIONS\n       \n      \n       ## builtin/merge-tree.c ##\n     -@@ builtin/merge-tree.c: static int trivial_merge(const char *base,\n     +@@ builtin/merge-tree.c: enum mode {\n       \n       struct merge_tree_options {\n       \tint mode;\n      +\tint allow_unrelated_histories;\n       \tint show_messages;\n     - \tint exclude_modes_oids_stages;\n     + \tint name_only;\n       };\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t * merge_incore_recursive in merge-ort.h\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \tfor (j = common; j; j = j->next)\n       \t\tcommit_list_insert(j->item, &merge_bases);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n     - \t\t\t   &o.exclude_modes_oids_stages,\n     - \t\t\t   N_(\"list conflicted files without modes/oids/stages\"),\n     + \t\t\t   &o.name_only,\n     + \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n       \t\t\t   PARSE_OPT_NONEG),\n      +\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n      +\t\t\t   &o.allow_unrelated_histories,\n     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char\n      \n       ## t/t4301-merge-tree-write-tree.sh ##\n      @@ t/t4301-merge-tree-write-tree.sh: test_expect_success setup '\n     - \t>whatever/empty &&\n     - \tgit add numbers greeting whatever/empty &&\n     + \tgit checkout side3 &&\n     + \tgit mv numbers sequence &&\n       \ttest_tick &&\n     --\tgit commit -m other-modifications\n     -+\tgit commit -m other-modifications &&\n     +-\tgit commit -m rename-numbers\n     ++\tgit commit -m rename-numbers &&\n      +\n      +\tgit switch --orphan unrelated &&\n      +\t>something-else &&\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success setup '\n      +\tgit commit -m first-commit\n       '\n       \n     - test_expect_success 'Content merge and a few conflicts' '\n     + test_expect_success 'Clean merge' '\n      @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'NUL terminated conflicted file \"lines\"' '\n       \ttest_cmp expect actual\n       '\n 15:  bc8591bbb63 = 12:  c279236ab65 git-merge-tree.txt: add a section on potentional usage mistakes\n\n-- \ngitgitgadget\n"},{"id":"448311","messageId":"d7b51da94e65db79aa59ca331e178741d3c50bc2.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 04/12] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:45Z","receivedAt":"2022-02-12T20:35:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean), 1 (conflicts present), anything else\n    (merge could not be performed; unknown if clean or conflicted)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability (meaning that NEWTREE would be a lot more than a tree in the\ncase of conflicts).\n\nThis also marks the traditional trivial merge of merge-tree as\ndeprecated.  The trivial merge not only had limited applicability, the\noutput format was also difficult to work with (and its format\nundocumented), and will generally be less performant than real merges.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  79 +++++++++++++++++++----\n builtin/merge-tree.c             |  44 ++++++++++++-\n t/t4301-merge-tree-write-tree.sh | 106 +++++++++++++++++++++++++++++++\n 3 files changed, 216 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..586733ea564 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,81 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The first form will merge the two branches, doing a real merge.  A real\n+merge is distinguished from a trivial merge in that it includes:\n+\n+  * three way content merges of individual files\n+  * rename detection\n+  * proper directory/file conflict handling\n+  * recursive ancestor consolidation (i.e. when there is more than one\n+    merge base, creating a virtual merge base by merging the merge bases)\n+  * etc.\n+\n+After the merge completes, the first form will create a new toplevel\n+tree object.  See `OUTPUT` below for details.\n+\n+The second form is deprecated; it is kept for backward compatibility\n+reasons but may be deleted in the future.  Other than the optional\n+`--trivial-merge`, it accepts no options.  It can only do a trivial\n+merge.  It reads three tree-ish, and outputs trivial merge results and\n+conflicting stages to the standard output in a semi-diff format.\n+Since this was designed for higher level scripts to consume and merge\n+the results back into the index, it omits entries that match\n+<branch1>.  The result of this second form is is similar to what\n+three-way 'git read-tree -m' does, but instead of storing the results\n+in the index, the command outputs the entries to the standard output.\n+This form not only has limited applicability, the output format is\n+also difficult to work with, and it will generally be less performant\n+than the first form even on successful merges (especially if working\n+in large repositories).  The remainder of this manual will only\n+discuss the first form.\n+\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1 (and the output is unspecified).\n+\n+USAGE NOTES\n+-----------\n+\n+git-merge-tree was written to be low-level plumbing, similar to\n+hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n+Thus, it could be used as a part of a series of steps such as\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 0f9d928e862..af445cb1576 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -398,7 +401,46 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *common;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct commit_list *j;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = branch1;\n+\topt.branch2 = branch2;\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tcommon = get_merge_bases(parent1, parent2);\n+\tif (!common)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\tputs(oid_to_hex(&result.tree->object.oid));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nnew file mode 100755\nindex 00000000000..e4f9421c105\n--- /dev/null\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+if test \"${GIT_TEST_MERGE_ALGORITHM}\" != \"ort\"\n+then\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+fi\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\tgit branch side3 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications &&\n+\n+\tgit checkout side3 &&\n+\tgit mv numbers sequence &&\n+\ttest_tick &&\n+\tgit commit -m rename-numbers\n+'\n+\n+test_expect_success 'Clean merge' '\n+\tgit merge-tree --write-tree side1 side3 >RESULT &&\n+\tq_to_tab <<-EOF >expect &&\n+\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n+\t100644 blob $(git rev-parse side1:numbers)Qsequence\n+\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n+\tEOF\n+\n+\tgit ls-tree $(cat RESULT) >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(cat .git/AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"448312","messageId":"fa55cb4d644ef7513001d88081f6db8df4265141.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 06/12] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:47Z","receivedAt":"2022-02-12T20:35:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, such as:\n  * \"Auto-merging <PATH>\"\n  * \"CONFLICT (content): ...\"\n  * \"CONFLICT (file/directory)\"\n  * etc.\nIn fact, when non-content conflicts occur (such as file/directory,\nmodify/delete, add/add with differing modes, rename/rename (1to2),\netc.), these informational messages may be the only notification the\nuser gets since these conflicts are not representable in the contents\nof the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----\n builtin/merge-tree.c             | 21 +++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 37 ++++++++++++++++++++++++++\n 3 files changed, 95 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 586733ea564..869c38f8223 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n@@ -47,17 +47,47 @@ than the first form even on successful merges (especially if working\n in large repositories).  The remainder of this manual will only\n discuss the first form.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+By default, for a successful merge, the output from git-merge-tree is\n+simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n+\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -79,6 +109,9 @@ Thus, it could be used as a part of a series of steps such as\n        NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n        git update-ref $BRANCH1 $NEWCOMMIT\n \n+Note that when the exit status is non-zero, NEWTREE in this sequence\n+will contain a lot more output than just a tree.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex af445cb1576..d44e8d087b1 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -396,6 +396,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -438,18 +439,27 @@ static int real_merge(struct merge_tree_options *o,\n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -459,10 +469,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    MODE_REAL),\n \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n \t/* Parse arguments */\n+\toriginal_argc = argc - 1; /* ignoring argv[0] */\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n \tswitch (o.mode) {\n@@ -486,8 +499,12 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \tcase MODE_TRIVIAL:\n \t\texpected_remaining_argc = 3;\n+\t\t/* Removal of `--trivial-merge` is expected */\n+\t\toriginal_argc--;\n \t\tbreak;\n \t}\n+\tif (o.mode == MODE_TRIVIAL && argc < original_argc)\n+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \tif (argc != expected_remaining_argc)\n \t\tusage_with_options(merge_tree_usage, mt_options);\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex e4f9421c105..25adcf36bdf 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -103,4 +103,41 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+anonymize_hash() {\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" \"$@\"\n+}\n+\n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n+for opt in $(git merge-tree --git-completion-helper-all)\n+do\n+\tif test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n+\tthen\n+\t\tcontinue\n+\tfi\n+\n+\ttest_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n+\t\ttest_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3\n+\t'\n+done\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448313","messageId":"58a5594aeb6e04e21409835e00c721a7417569df.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:46Z","receivedAt":"2022-02-12T20:35:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis patch includes no new code; it simply moves a bunch of lines into a\nnew function.  As such, there are no functional changes.  This is just a\npreparatory step to allow the printed messages to be handled differently\nby other callers, such as in `git merge-tree --write-tree`.\n\n(Patch best viewed with\n     --color-moved --color-moved-ws=allow-indentation-change\n to see that it is a simple code movement.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 78 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 37 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9bf15a01db8..ebaed98d53a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4272,43 +4311,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\tfclose(fp);\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n-\n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"448314","messageId":"f3ad7add5154d9d33291df997350b2b5db88260e.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 07/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:48Z","receivedAt":"2022-02-12T20:35:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u`, which means\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex ebaed98d53a..e1b647b0a40 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4274,6 +4274,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..ddcc39d7270 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -88,6 +89,26 @@ void merge_switch_to_result(struct merge_options *opt,\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"448315","messageId":"6058190d1b16a689ac5e4b4c077b594acf13acea.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 08/12] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:49Z","receivedAt":"2022-02-12T20:35:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  8 ++++++++\n builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 11 +++++++++++\n 3 files changed, 41 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 869c38f8223..9f7eb03c6eb 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -67,6 +67,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -78,6 +79,13 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex d44e8d087b1..cb4169d2271 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -400,7 +403,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *common;\n@@ -444,6 +448,22 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n \t\tprintf(\"\\n\");\n \t\tmerge_display_update_messages(&opt, &result);\n@@ -511,7 +531,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == MODE_REAL)\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 25adcf36bdf..0964c1145e6 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -117,6 +117,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -140,4 +142,13 @@ do\n \t'\n done\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448316","messageId":"435f66ea699ff0ff553350bb5c9d6097940794e9.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 09/12] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:50Z","receivedAt":"2022-02-12T20:35:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide a --name-only option for users to exclude the mode, oid, and\nstage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 32 ++++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++++--\n 3 files changed, 60 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 9f7eb03c6eb..6502ee0669e 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -50,6 +50,13 @@ discuss the first form.\n OPTIONS\n -------\n \n+--name-only::\n+\tIn the Conflicted file info section, instead of writing a list\n+\tof (mode, oid, stage, path) tuples to output for conflicted\n+\tfiles, just provide a list of filenames with conflicts (and\n+\tdo not list filenames multiple times if they have multiple\n+\tconflicting stages).\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -67,7 +74,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -79,18 +86,23 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--name-only` option is passed, the mode, object, and stage will\n+be omitted.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -120,6 +132,14 @@ Thus, it could be used as a part of a series of steps such as\n Note that when the exit status is non-zero, NEWTREE in this sequence\n will contain a lot more output than just a tree.\n \n+git-merge-tree was written to provide users with the same information\n+that they'd have access to if using `git merge`:\n+  * what would be written to the working tree (the <OID of toplevel tree>)\n+  * the higher order stages that would be written to the index (the\n+    <Conflicted file info>)\n+  * any messages that would have been printed to stdout (the <Informational\n+    messages>)\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex cb4169d2271..1d4d6637b90 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -400,6 +400,7 @@ enum mode {\n struct merge_tree_options {\n \tint mode;\n \tint show_messages;\n+\tint name_only;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -456,7 +457,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->name_only)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -491,6 +496,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F(0, \"name-only\",\n+\t\t\t   &o.name_only,\n+\t\t\t   N_(\"list filenames without modes/oids/stages\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 0964c1145e6..4ee85439372 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -65,6 +65,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(cat .git/AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -108,7 +109,7 @@ anonymize_hash() {\n }\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \t# Expected results:\n@@ -143,7 +144,7 @@ do\n done\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -151,4 +152,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448317","messageId":"c279236ab655c7cfc851e8555acb355c42c4e5bc.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 12/12] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:53Z","receivedAt":"2022-02-12T20:35:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 3f566477dcb..4520bbf020a 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -158,6 +158,52 @@ that they'd have access to if using `git merge`:\n   * any messages that would have been printed to stdout (the <Informational\n     messages>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <Conflicted file info> section instead.  Not\n+only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n+check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <Conflicted file info> list.  The information there is insufficient\n+to do so.  For example: Rename/rename(1to2) conflicts (both sides\n+renamed the same file differently) will result in three different file\n+having higher order stages (but each only has one higher order stage),\n+with no way (short of the <Informational messages> section) to determine\n+which three files are related.  File/directory conflicts also result in\n+a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<Informational messages> section has the necessary info, though it is\n+not designed to be machine parseable.\n+\n+Do NOT assume all filenames listed in the <Informational messages>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <Conflicted file info> and re-merging\n+them to present the conflicts to the user.  This will lose information.\n+Instead, look up the version of the file found within the <OID of\n+toplevel tree> and show that instead.  In particular, the latter will\n+have conflict markers annotated with the original branch/commit being\n+merged and, if renames were involved, the original filename.  While you\n+could include the original branch/commit in the conflict marker\n+annotations when re-merging, the original filename is not available from\n+the <Conflicted file info> and thus you would be losing information that\n+might help the user resolve the conflict.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n"},{"id":"448318","messageId":"5f253e298b39a9af3f7f9bd473c63ef9ca34b2df.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 10/12] merge-tree: allow `ls-files -u` style info to be NUL terminated","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:51Z","receivedAt":"2022-02-12T20:35:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch as `git ls-files` has a -z option, let's add one to merge-tree so\nthat the conflict-info section can be NUL terminated (and avoid quoting\nof unusual filenames).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 21 +++++++++++++----\n builtin/merge-tree.c             |  6 +++--\n t/t4301-merge-tree-write-tree.sh | 40 ++++++++++++++++++++++++++++++++\n 3 files changed, 61 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 6502ee0669e..ada4595b4fc 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -50,6 +50,12 @@ discuss the first form.\n OPTIONS\n -------\n \n+-z::\n+\tDo not quote filenames in the <Conflicted file info> section,\n+\tand end each filename with a NUL character rather than\n+\tnewline.  Also begin the messages section with a NUL character\n+\tinstead of a newline.  See OUTPUT below for more information.\n+\n --name-only::\n \tIn the Conflicted file info section, instead of writing a list\n \tof (mode, oid, stage, path) tuples to output for conflicted\n@@ -84,7 +90,8 @@ OID of toplevel tree\n \n This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n-files within this tree may have embedded conflict markers.\n+files within this tree may have embedded conflict markers.  This section\n+is always followed by a newline (or NUL if `-z` is passed).\n \n Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n@@ -96,19 +103,25 @@ This is a sequence of lines with the format\n The filename will be quoted as explained for the configuration\n variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n the `--name-only` option is passed, the mode, object, and stage will\n-be omitted.\n+be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n+character instead of a newline character.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n-This always starts with a blank line to separate it from the previous\n-sections, and then has free-form messages about the merge, such as:\n+This always starts with a blank line (or NUL if `-z` is passed) to\n+separate it from the previous sections, and then has free-form\n+messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n   * \"Failed to merge submodule <submodule> (<reason>)\"\n   * \"Warning: cannot merge binary files: <filename>\"\n \n+Note that these free-form messages will never have a NUL character\n+in or between them, even if -z is passed.  It is simply a large block\n+of text taking up the remainder of the output.\n+\n EXIT STATUS\n -----------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 1d4d6637b90..825255667b1 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -448,7 +448,7 @@ static int real_merge(struct merge_tree_options *o,\n \tif (o->show_messages == -1)\n \t\to->show_messages = !result.clean;\n \n-\tputs(oid_to_hex(&result.tree->object.oid));\n+\tprintf(\"%s%c\", oid_to_hex(&result.tree->object.oid), line_termination);\n \tif (!result.clean) {\n \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n \t\tconst char *last = NULL;\n@@ -470,7 +470,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\tstring_list_clear(&conflicted_files, 1);\n \t}\n \tif (o->show_messages) {\n-\t\tprintf(\"\\n\");\n+\t\tputchar(line_termination);\n \t\tmerge_display_update_messages(&opt, &result);\n \t}\n \tmerge_finalize(&opt, &result);\n@@ -496,6 +496,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_SET_INT('z', NULL, &line_termination,\n+\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n \t\tOPT_BOOL_F(0, \"name-only\",\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 4ee85439372..fe476ed1bcc 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -173,4 +173,44 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'NUL terminated conflicted file \"lines\"' '\n+\tgit checkout -b tweak1 side1 &&\n+\ttest_write_lines zero 1 2 3 4 5 6 >numbers &&\n+\tgit add numbers &&\n+\tgit mv numbers \"Αυτά μου φαίνονται κινέζικα\" &&\n+\tgit commit -m \"Renamed numbers\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\t#   \"Αυτά μου φαίνονται κινέζικα\" should have a conflict\n+\techo HASH | lf_to_nul >expect &&\n+\n+\tq_to_tab <<-EOF | lf_to_nul >>expect &&\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~tweak1\n+\t100644 HASH 2Qwhatever~tweak1\n+\t100644 HASH 1QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 2QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 3QΑυτά μου φαίνονται κινέζικα\n+\n+\tEOF\n+\n+\tcat <<-EOF >>expect &&\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n+\tCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n+\tAuto-merging Αυτά μου φαίνονται κινέζικα\n+\tCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448319","messageId":"e706cf31c6eec8e54b8791a19efa3388c662f711.1644698093.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v4 11/12] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-12T20:34:52Z","receivedAt":"2022-02-12T20:35:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex ada4595b4fc..3f566477dcb 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -69,6 +69,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n OUTPUT\n ------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 825255667b1..911504ad694 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -399,6 +399,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint name_only;\n };\n@@ -436,7 +437,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common)\n+\tif (!common && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tfor (j = common; j; j = j->next)\n \t\tcommit_list_insert(j->item, &merge_bases);\n@@ -502,6 +503,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex fe476ed1bcc..5cb546083d7 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -44,7 +44,13 @@ test_expect_success setup '\n \tgit checkout side3 &&\n \tgit mv numbers sequence &&\n \ttest_tick &&\n-\tgit commit -m rename-numbers\n+\tgit commit -m rename-numbers &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Clean merge' '\n@@ -213,4 +219,20 @@ test_expect_success 'NUL terminated conflicted file \"lines\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448374","messageId":"xmqqwnhx2v6i.fsf@gitster.g","threadId":"57288","inReplyTo":"d7b51da94e65db79aa59ca331e178741d3c50bc2.1644698093.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 04/12] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-14T17:51:33Z","receivedAt":"2022-02-14T17:51:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +<branch1>.  The result of this second form is is similar to what\n\nI haven't read the rest of the patches, but this like somehow stood\nout in the interdiff.\n\n"},{"id":"448408","messageId":"CABPp-BHQv-j=Y93WJ8A-y1+0ybOaemByhwe6twZYKEwTTk9bDA@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqwnhx2v6i.fsf@gitster.g","subject":"Re: [PATCH v4 04/12] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-15T06:03:46Z","receivedAt":"2022-02-15T06:04:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 14, 2022 at 9:51 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +<branch1>.  The result of this second form is is similar to what\n>\n> I haven't read the rest of the patches, but this like somehow stood\n> out in the interdiff.\n\nAre you highlighting the double \"is\" that I need to correct here?  Or\nwas there something else that stood out to you?\n"},{"id":"448423","messageId":"220215.864k50y0g5.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"d7b51da94e65db79aa59ca331e178741d3c50bc2.1644698093.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 04/12] merge-tree: implement real merges","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-15T08:46:54Z","receivedAt":"2022-02-15T08:54:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Feb 12 2022, Elijah Newren via GitGitGadget wrote:\n\n> +# This test is ort-specific\n> +if test \"${GIT_TEST_MERGE_ALGORITHM}\" != \"ort\"\n\nNit: Needless braces, left over from an earlier version where you used ${VAR:+...} ?\n\n> +test_expect_success 'Clean merge' '\n> +\tgit merge-tree --write-tree side1 side3 >RESULT &&\n> +\tq_to_tab <<-EOF >expect &&\n> +\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n> +\t100644 blob $(git rev-parse side1:numbers)Qsequence\n> +\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n> +\tEOF\n> +\n> +\tgit ls-tree $(cat RESULT) >actual &&\n\nNit: to avoid the \"cat\":\n\n    oid=$(git merge-tree ...) &&\n    [...]\n    git ls-tree $oid [...]\n\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'Content merge and a few conflicts' '\n> +\tgit checkout side1^0 &&\n> +\ttest_must_fail git merge side2 &&\n> +\texpected_tree=$(cat .git/AUTO_MERGE) &&\n\nLet's do \"git rev-parse AUTO_MERGE\", to avoid needing REFFILES here.\n\n> [...]\n> +\t# greeting should have a merge conflict\n> +\tgit show ${expected_tree}:greeting >tmp &&\n> +\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n\nNit: More needless \"cat\", can just be: \"sed ... <tmp >expect\".\n"},{"id":"448880","messageId":"4a7cd5542bb2f89b4874e4115542ccee9c4639af.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 01/12] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:31Z","receivedAt":"2022-02-20T06:54:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"448881","messageId":"4780ff6784d426bf0a96859ef9bf9c14e87d5f50.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 02/12] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:32Z","receivedAt":"2022-02-20T06:54:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"448882","messageId":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v4.git.1644698093.gitgitgadget@gmail.com","subject":"[PATCH v5 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:30Z","receivedAt":"2022-02-20T06:54:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs. It could\nbe used to do merges when in a bare repository (thus potentially making it\nof interest to Git hosting sites, i.e. \"Server side merges\"), or for doing a\nmerge of branches that aren't checked out.\n\nIt does not handle similar functionality for cherry-picks, rebases, or\nreverts; that is also of interest, but is being deferred for a future\nseries.\n\n== Quick Overview ==\n\n * Patches 1-2: preparatory cleanups\n * Patches 3-4: implement basic real merges\n * Patches 5-6: include informational messages (\"CONFLICT\" messages and\n   such) in output\n * Patches 7-10: add ability to include ls-files -u style of info in the\n   output\n * Patch 11: support --allow-unrelated-histories\n * Patch 12: augment the manual with potential usage mistakes\n\n== Updates Log ==\n\nMany thanks to the many reviewers who provided good feedback on the most\nrecent round -- Junio, Ævar, Josh, Emily, and perhaps some others I've\nforgotten from review club.\n\nUpdates since v4:\n\n * Fixed double \"is\" in documentation.\n * Fixed a few small items with testcases\n\nUpdates since v3 (or v5, if you include the rounds at\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/):\n\n * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n   being used and opened a can of worms[1]\n * [Patch 3] Restructured argument checking, including using an enum\n * [Patch 4] Restored the extended paragraph about the deprecated form of\n   git-merge-tree, mentioned write-tree in plumbing commands, and a few\n   other small fixups to the documentation\n * [Patch 4] Also provide an example of a clean merge rather than just a\n   conflicted one\n * [Patch 6] Fix the incompatible arguments check and add some tests for it\n * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n   easier to read (less repeated sed)\n * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n   option for now\n * [Patch 10] When -z passed, the tree in the first section should have a\n   trailing NUL rather than trailing newline [1]\n   https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n\nStuff NOT included that reviewers brought up in earlier rounds:\n\n * Very generic (mode, oid, stage, filename) printing formatting[2]\n * Always printing 3 stages for each filename with conflicts[3]\n * Attempting to group conflict stages by logical conflict rather than by\n   affected target filepath[4]\n * Providing similar functionality for doing cherry-picks/rebases/reverts,\n   i.e. a scheme for three-way merges with a specified merge-base[5]. That's\n   being deferred to a future series. [2]\n   https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n   [3]\n   https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n   [4]\n   https://lore.kernel.org/git/CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com/\n   [5]\n   https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n\nUpdates since v2:\n\n * Improved patches from Dscho for the diff_warn_rename_limit() handling\n * Add a -z option for NUL-terminated conflict info lines (so that filenames\n   do not have to be quoted)\n\nUpdates since v1 (or v3 depending on how you count; thanks to René, Ævar,\nChristian, Dscho for very helpful feedback):\n\n * New patch from Dscho allowing diff_warn_rename_limit() to print somewhere\n   other than stdout (I hope he's okay with me including his Signed-off-by)\n * Now prints filenames relative to prefix, much like ls-files\n * Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave\n   it a -l shorthand; I'm wondering if I should just drop this option,\n   though.\n * And numerous cleanups, in lots of areas:\n   * Multiple parse-options cleanups\n   * Lots of commit message cleanups\n   * Wording tweaks to the \"Description\" section of the manual\n   * Several small code cleanups\n * I dropped the RFC label\n\nUpdates since original submission v2 (thanks to Christian, Dscho, Ramsay,\nand René for suggestions and comments):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since original submission v1 (thanks to Johannes Altmanninger and\nFabian for suggestions):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\nElijah Newren (12):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-tree: allow `ls-files -u` style info to be NUL terminated\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\n Documentation/git-merge-tree.txt | 204 ++++++++++++++++++++++++--\n builtin/merge-tree.c             | 189 ++++++++++++++++++++++--\n git.c                            |   2 +-\n merge-ort.c                      | 109 +++++++++-----\n merge-ort.h                      |  29 ++++\n t/t4301-merge-tree-write-tree.sh | 238 +++++++++++++++++++++++++++++++\n 6 files changed, 709 insertions(+), 62 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\n\nbase-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n\nRange-diff vs v4:\n\n  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()\n  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function\n  3:  60253745f5c =  3:  60253745f5c merge-tree: add option parsing and initial shell for real merge function\n  4:  d7b51da94e6 !  4:  7994775a934 merge-tree: implement real merges\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +conflicting stages to the standard output in a semi-diff format.\n      +Since this was designed for higher level scripts to consume and merge\n      +the results back into the index, it omits entries that match\n     -+<branch1>.  The result of this second form is is similar to what\n     ++<branch1>.  The result of this second form is similar to what\n      +three-way 'git read-tree -m' does, but instead of storing the results\n      +in the index, the command outputs the entries to the standard output.\n      +This form not only has limited applicability, the output format is\n     @@ t/t4301-merge-tree-write-tree.sh (new)\n      +. ./test-lib.sh\n      +\n      +# This test is ort-specific\n     -+if test \"${GIT_TEST_MERGE_ALGORITHM}\" != \"ort\"\n     ++if test \"$GIT_TEST_MERGE_ALGORITHM\" != \"ort\"\n      +then\n      +\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n      +\ttest_done\n     @@ t/t4301-merge-tree-write-tree.sh (new)\n      +'\n      +\n      +test_expect_success 'Clean merge' '\n     -+\tgit merge-tree --write-tree side1 side3 >RESULT &&\n     ++\tTREE_OID=$(git merge-tree --write-tree side1 side3) &&\n      +\tq_to_tab <<-EOF >expect &&\n      +\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n      +\t100644 blob $(git rev-parse side1:numbers)Qsequence\n      +\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n      +\tEOF\n      +\n     -+\tgit ls-tree $(cat RESULT) >actual &&\n     ++\tgit ls-tree $TREE_OID >actual &&\n      +\ttest_cmp expect actual\n      +'\n      +\n      +test_expect_success 'Content merge and a few conflicts' '\n      +\tgit checkout side1^0 &&\n      +\ttest_must_fail git merge side2 &&\n     -+\texpected_tree=$(cat .git/AUTO_MERGE) &&\n     ++\texpected_tree=$(git rev-parse AUTO_MERGE) &&\n      +\n      +\t# We will redo the merge, while we are still in a conflicted state!\n      +\ttest_when_finished \"git reset --hard\" &&\n     @@ t/t4301-merge-tree-write-tree.sh (new)\n      +\n      +\t# greeting should have a merge conflict\n      +\tgit show ${expected_tree}:greeting >tmp &&\n     -+\tcat tmp | sed -e s/HEAD/side1/ >expect &&\n     ++\tsed -e s/HEAD/side1/ tmp >expect &&\n      +\tgit show ${actual_tree}:greeting >actual &&\n      +\ttest_cmp expect actual\n      +'\n  5:  58a5594aeb6 =  5:  e0f95e094cf merge-ort: split out a separate display_update_messages() function\n  6:  fa55cb4d644 =  6:  90c4adecb23 merge-tree: support including merge messages in output\n  7:  f3ad7add515 =  7:  12e2351092a merge-ort: provide a merge_get_conflicted_files() helper function\n  8:  6058190d1b1 =  8:  5bb7d3725ad merge-tree: provide a list of which files have conflicts\n  9:  435f66ea699 !  9:  3c2ca198cec merge-tree: provide easy access to `ls-files -u` style info\n     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char\n      \n       ## t/t4301-merge-tree-write-tree.sh ##\n      @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Content merge and a few conflicts' '\n     - \texpected_tree=$(cat .git/AUTO_MERGE) &&\n     + \texpected_tree=$(git rev-parse AUTO_MERGE) &&\n       \n       \t# We will redo the merge, while we are still in a conflicted state!\n      +\tgit ls-files -u >conflicted-file-info &&\n 10:  5f253e298b3 = 10:  6e89e17693a merge-tree: allow `ls-files -u` style info to be NUL terminated\n 11:  e706cf31c6e = 11:  6ddd5ffde9c merge-tree: add a --allow-unrelated-histories flag\n 12:  c279236ab65 = 12:  7abf633b638 git-merge-tree.txt: add a section on potentional usage mistakes\n\n-- \ngitgitgadget\n"},{"id":"448883","messageId":"7994775a9341b256d1ea7dfc417bb577d9a3195f.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 04/12] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:34Z","receivedAt":"2022-02-20T06:54:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean), 1 (conflicts present), anything else\n    (merge could not be performed; unknown if clean or conflicted)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability (meaning that NEWTREE would be a lot more than a tree in the\ncase of conflicts).\n\nThis also marks the traditional trivial merge of merge-tree as\ndeprecated.  The trivial merge not only had limited applicability, the\noutput format was also difficult to work with (and its format\nundocumented), and will generally be less performant than real merges.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  79 +++++++++++++++++++----\n builtin/merge-tree.c             |  44 ++++++++++++-\n t/t4301-merge-tree-write-tree.sh | 106 +++++++++++++++++++++++++++++++\n 3 files changed, 216 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..589a83738ce 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,81 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The first form will merge the two branches, doing a real merge.  A real\n+merge is distinguished from a trivial merge in that it includes:\n+\n+  * three way content merges of individual files\n+  * rename detection\n+  * proper directory/file conflict handling\n+  * recursive ancestor consolidation (i.e. when there is more than one\n+    merge base, creating a virtual merge base by merging the merge bases)\n+  * etc.\n+\n+After the merge completes, the first form will create a new toplevel\n+tree object.  See `OUTPUT` below for details.\n+\n+The second form is deprecated; it is kept for backward compatibility\n+reasons but may be deleted in the future.  Other than the optional\n+`--trivial-merge`, it accepts no options.  It can only do a trivial\n+merge.  It reads three tree-ish, and outputs trivial merge results and\n+conflicting stages to the standard output in a semi-diff format.\n+Since this was designed for higher level scripts to consume and merge\n+the results back into the index, it omits entries that match\n+<branch1>.  The result of this second form is similar to what\n+three-way 'git read-tree -m' does, but instead of storing the results\n+in the index, the command outputs the entries to the standard output.\n+This form not only has limited applicability, the output format is\n+also difficult to work with, and it will generally be less performant\n+than the first form even on successful merges (especially if working\n+in large repositories).  The remainder of this manual will only\n+discuss the first form.\n+\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1 (and the output is unspecified).\n+\n+USAGE NOTES\n+-----------\n+\n+git-merge-tree was written to be low-level plumbing, similar to\n+hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n+Thus, it could be used as a part of a series of steps such as\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 0f9d928e862..af445cb1576 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -398,7 +401,46 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *common;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct commit_list *j;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = branch1;\n+\topt.branch2 = branch2;\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tcommon = get_merge_bases(parent1, parent2);\n+\tif (!common)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tfor (j = common; j; j = j->next)\n+\t\tcommit_list_insert(j->item, &merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\tputs(oid_to_hex(&result.tree->object.oid));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nnew file mode 100755\nindex 00000000000..6d321652e21\n--- /dev/null\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+if test \"$GIT_TEST_MERGE_ALGORITHM\" != \"ort\"\n+then\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+fi\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\tgit branch side3 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications &&\n+\n+\tgit checkout side3 &&\n+\tgit mv numbers sequence &&\n+\ttest_tick &&\n+\tgit commit -m rename-numbers\n+'\n+\n+test_expect_success 'Clean merge' '\n+\tTREE_OID=$(git merge-tree --write-tree side1 side3) &&\n+\tq_to_tab <<-EOF >expect &&\n+\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n+\t100644 blob $(git rev-parse side1:numbers)Qsequence\n+\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n+\tEOF\n+\n+\tgit ls-tree $TREE_OID >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(git rev-parse AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tsed -e s/HEAD/side1/ tmp >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"448884","messageId":"60253745f5c59ac4c75d46df1f98ee722523f166.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:33Z","receivedAt":"2022-02-20T06:55:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 84 +++++++++++++++++++++++++++++++++++++++-----\n git.c                |  2 +-\n 2 files changed, 76 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..0f9d928e862 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,74 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+enum mode {\n+\tMODE_UNKNOWN,\n+\tMODE_TRIVIAL,\n+\tMODE_REAL,\n+};\n+\n+struct merge_tree_options {\n+\tint mode;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n+\t\t\t    MODE_REAL),\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n+\t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tswitch (o.mode) {\n+\tdefault:\n+\t\tBUG(\"unexpected command mode %d\", o.mode);\n+\tcase MODE_UNKNOWN:\n+\t\tswitch (argc) {\n+\t\tdefault:\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\tcase 2:\n+\t\t\to.mode = MODE_REAL;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\to.mode = MODE_TRIVIAL;\n+\t\t\tbreak;\n+\t\t}\n+\t\texpected_remaining_argc = argc;\n+\t\tbreak;\n+\tcase MODE_REAL:\n+\t\texpected_remaining_argc = 2;\n+\t\tbreak;\n+\tcase MODE_TRIVIAL:\n+\t\texpected_remaining_argc = 3;\n+\t\tbreak;\n+\t}\n+\n+\tif (argc != expected_remaining_argc)\n+\t\tusage_with_options(merge_tree_usage, mt_options);\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.mode == MODE_REAL)\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..6090a1289db 100644\n--- a/git.c\n+++ b/git.c\n@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"448885","messageId":"e0f95e094cfa5b63c8a1da56156725fc560b29e2.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:35Z","receivedAt":"2022-02-20T06:55:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis patch includes no new code; it simply moves a bunch of lines into a\nnew function.  As such, there are no functional changes.  This is just a\npreparatory step to allow the printed messages to be handled differently\nby other callers, such as in `git merge-tree --write-tree`.\n\n(Patch best viewed with\n     --color-moved --color-moved-ws=allow-indentation-change\n to see that it is a simple code movement.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 78 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 37 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9bf15a01db8..ebaed98d53a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4272,43 +4311,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\tfclose(fp);\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n-\n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"448886","messageId":"5bb7d3725ad529c36b1bc0367d67dc16a05b14b3.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 08/12] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:38Z","receivedAt":"2022-02-20T06:55:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  8 ++++++++\n builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 11 +++++++++++\n 3 files changed, 41 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex f415d6de5a3..deaeb49ae05 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -67,6 +67,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -78,6 +79,13 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex d44e8d087b1..cb4169d2271 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -400,7 +403,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *common;\n@@ -444,6 +448,22 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n \t\tprintf(\"\\n\");\n \t\tmerge_display_update_messages(&opt, &result);\n@@ -511,7 +531,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == MODE_REAL)\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 719d81e7173..8e6dba44288 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -117,6 +117,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -140,4 +142,13 @@ do\n \t'\n done\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448887","messageId":"90c4adecb23770febc204b9061c8da0cd517c8a2.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 06/12] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:36Z","receivedAt":"2022-02-20T06:55:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, such as:\n  * \"Auto-merging <PATH>\"\n  * \"CONFLICT (content): ...\"\n  * \"CONFLICT (file/directory)\"\n  * etc.\nIn fact, when non-content conflicts occur (such as file/directory,\nmodify/delete, add/add with differing modes, rename/rename (1to2),\netc.), these informational messages may be the only notification the\nuser gets since these conflicts are not representable in the contents\nof the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----\n builtin/merge-tree.c             | 21 +++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 37 ++++++++++++++++++++++++++\n 3 files changed, 95 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 589a83738ce..f415d6de5a3 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n DESCRIPTION\n@@ -47,17 +47,47 @@ than the first form even on successful merges (especially if working\n in large repositories).  The remainder of this manual will only\n discuss the first form.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+By default, for a successful merge, the output from git-merge-tree is\n+simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n+\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -79,6 +109,9 @@ Thus, it could be used as a part of a series of steps such as\n        NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n        git update-ref $BRANCH1 $NEWCOMMIT\n \n+Note that when the exit status is non-zero, NEWTREE in this sequence\n+will contain a lot more output than just a tree.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex af445cb1576..d44e8d087b1 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -396,6 +396,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -438,18 +439,27 @@ static int real_merge(struct merge_tree_options *o,\n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -459,10 +469,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    MODE_REAL),\n \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n \t/* Parse arguments */\n+\toriginal_argc = argc - 1; /* ignoring argv[0] */\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n \tswitch (o.mode) {\n@@ -486,8 +499,12 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \tcase MODE_TRIVIAL:\n \t\texpected_remaining_argc = 3;\n+\t\t/* Removal of `--trivial-merge` is expected */\n+\t\toriginal_argc--;\n \t\tbreak;\n \t}\n+\tif (o.mode == MODE_TRIVIAL && argc < original_argc)\n+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \tif (argc != expected_remaining_argc)\n \t\tusage_with_options(merge_tree_usage, mt_options);\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 6d321652e21..719d81e7173 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -103,4 +103,41 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+anonymize_hash() {\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" \"$@\"\n+}\n+\n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n+for opt in $(git merge-tree --git-completion-helper-all)\n+do\n+\tif test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n+\tthen\n+\t\tcontinue\n+\tfi\n+\n+\ttest_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n+\t\ttest_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3\n+\t'\n+done\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448888","messageId":"12e2351092a00d2b5efaa3773771b200c6d52600.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 07/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:37Z","receivedAt":"2022-02-20T06:55:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u`, which means\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex ebaed98d53a..e1b647b0a40 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4274,6 +4274,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..ddcc39d7270 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -88,6 +89,26 @@ void merge_switch_to_result(struct merge_options *opt,\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"448889","messageId":"6ddd5ffde9c406a24f197c6b1a9e00192ce59d53.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 11/12] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:41Z","receivedAt":"2022-02-20T06:55:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 44024b11b1c..d2ff2fa3035 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -69,6 +69,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n OUTPUT\n ------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 825255667b1..911504ad694 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -399,6 +399,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint name_only;\n };\n@@ -436,7 +437,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common)\n+\tif (!common && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tfor (j = common; j; j = j->next)\n \t\tcommit_list_insert(j->item, &merge_bases);\n@@ -502,6 +503,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 22e03f0939c..bd1769c624b 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -44,7 +44,13 @@ test_expect_success setup '\n \tgit checkout side3 &&\n \tgit mv numbers sequence &&\n \ttest_tick &&\n-\tgit commit -m rename-numbers\n+\tgit commit -m rename-numbers &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Clean merge' '\n@@ -213,4 +219,20 @@ test_expect_success 'NUL terminated conflicted file \"lines\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448890","messageId":"7abf633b6382118a63e983b80186e91dc38eef5f.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 12/12] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:42Z","receivedAt":"2022-02-20T06:55:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex d2ff2fa3035..306149fa0e2 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -158,6 +158,52 @@ that they'd have access to if using `git merge`:\n   * any messages that would have been printed to stdout (the <Informational\n     messages>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <Conflicted file info> section instead.  Not\n+only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n+check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <Conflicted file info> list.  The information there is insufficient\n+to do so.  For example: Rename/rename(1to2) conflicts (both sides\n+renamed the same file differently) will result in three different file\n+having higher order stages (but each only has one higher order stage),\n+with no way (short of the <Informational messages> section) to determine\n+which three files are related.  File/directory conflicts also result in\n+a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<Informational messages> section has the necessary info, though it is\n+not designed to be machine parseable.\n+\n+Do NOT assume all filenames listed in the <Informational messages>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <Conflicted file info> and re-merging\n+them to present the conflicts to the user.  This will lose information.\n+Instead, look up the version of the file found within the <OID of\n+toplevel tree> and show that instead.  In particular, the latter will\n+have conflict markers annotated with the original branch/commit being\n+merged and, if renames were involved, the original filename.  While you\n+could include the original branch/commit in the conflict marker\n+annotations when re-merging, the original filename is not available from\n+the <Conflicted file info> and thus you would be losing information that\n+might help the user resolve the conflict.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n-- \ngitgitgadget\n"},{"id":"448891","messageId":"6e89e17693a1fd46f99805e02fadba98b1ce93f1.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 10/12] merge-tree: allow `ls-files -u` style info to be NUL terminated","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:40Z","receivedAt":"2022-02-20T06:55:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch as `git ls-files` has a -z option, let's add one to merge-tree so\nthat the conflict-info section can be NUL terminated (and avoid quoting\nof unusual filenames).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 21 +++++++++++++----\n builtin/merge-tree.c             |  6 +++--\n t/t4301-merge-tree-write-tree.sh | 40 ++++++++++++++++++++++++++++++++\n 3 files changed, 61 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 67a135e8f5d..44024b11b1c 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -50,6 +50,12 @@ discuss the first form.\n OPTIONS\n -------\n \n+-z::\n+\tDo not quote filenames in the <Conflicted file info> section,\n+\tand end each filename with a NUL character rather than\n+\tnewline.  Also begin the messages section with a NUL character\n+\tinstead of a newline.  See OUTPUT below for more information.\n+\n --name-only::\n \tIn the Conflicted file info section, instead of writing a list\n \tof (mode, oid, stage, path) tuples to output for conflicted\n@@ -84,7 +90,8 @@ OID of toplevel tree\n \n This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n-files within this tree may have embedded conflict markers.\n+files within this tree may have embedded conflict markers.  This section\n+is always followed by a newline (or NUL if `-z` is passed).\n \n Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n@@ -96,19 +103,25 @@ This is a sequence of lines with the format\n The filename will be quoted as explained for the configuration\n variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n the `--name-only` option is passed, the mode, object, and stage will\n-be omitted.\n+be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n+character instead of a newline character.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n-This always starts with a blank line to separate it from the previous\n-sections, and then has free-form messages about the merge, such as:\n+This always starts with a blank line (or NUL if `-z` is passed) to\n+separate it from the previous sections, and then has free-form\n+messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n   * \"Failed to merge submodule <submodule> (<reason>)\"\n   * \"Warning: cannot merge binary files: <filename>\"\n \n+Note that these free-form messages will never have a NUL character\n+in or between them, even if -z is passed.  It is simply a large block\n+of text taking up the remainder of the output.\n+\n EXIT STATUS\n -----------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 1d4d6637b90..825255667b1 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -448,7 +448,7 @@ static int real_merge(struct merge_tree_options *o,\n \tif (o->show_messages == -1)\n \t\to->show_messages = !result.clean;\n \n-\tputs(oid_to_hex(&result.tree->object.oid));\n+\tprintf(\"%s%c\", oid_to_hex(&result.tree->object.oid), line_termination);\n \tif (!result.clean) {\n \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n \t\tconst char *last = NULL;\n@@ -470,7 +470,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\tstring_list_clear(&conflicted_files, 1);\n \t}\n \tif (o->show_messages) {\n-\t\tprintf(\"\\n\");\n+\t\tputchar(line_termination);\n \t\tmerge_display_update_messages(&opt, &result);\n \t}\n \tmerge_finalize(&opt, &result);\n@@ -496,6 +496,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_SET_INT('z', NULL, &line_termination,\n+\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n \t\tOPT_BOOL_F(0, \"name-only\",\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 0ec5f0d3f7e..22e03f0939c 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -173,4 +173,44 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'NUL terminated conflicted file \"lines\"' '\n+\tgit checkout -b tweak1 side1 &&\n+\ttest_write_lines zero 1 2 3 4 5 6 >numbers &&\n+\tgit add numbers &&\n+\tgit mv numbers \"Αυτά μου φαίνονται κινέζικα\" &&\n+\tgit commit -m \"Renamed numbers\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\t#   \"Αυτά μου φαίνονται κινέζικα\" should have a conflict\n+\techo HASH | lf_to_nul >expect &&\n+\n+\tq_to_tab <<-EOF | lf_to_nul >>expect &&\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~tweak1\n+\t100644 HASH 2Qwhatever~tweak1\n+\t100644 HASH 1QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 2QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 3QΑυτά μου φαίνονται κινέζικα\n+\n+\tEOF\n+\n+\tcat <<-EOF >>expect &&\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n+\tCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n+\tAuto-merging Αυτά μου φαίνονται κινέζικα\n+\tCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448892","messageId":"3c2ca198cec4986cf2d7651d854eef5542e7084d.1645340082.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v5 09/12] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-20T06:54:39Z","receivedAt":"2022-02-20T06:55:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide a --name-only option for users to exclude the mode, oid, and\nstage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 32 ++++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++++--\n 3 files changed, 60 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex deaeb49ae05..67a135e8f5d 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -50,6 +50,13 @@ discuss the first form.\n OPTIONS\n -------\n \n+--name-only::\n+\tIn the Conflicted file info section, instead of writing a list\n+\tof (mode, oid, stage, path) tuples to output for conflicted\n+\tfiles, just provide a list of filenames with conflicts (and\n+\tdo not list filenames multiple times if they have multiple\n+\tconflicting stages).\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -67,7 +74,7 @@ simply one line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -79,18 +86,23 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--name-only` option is passed, the mode, object, and stage will\n+be omitted.\n \n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -120,6 +132,14 @@ Thus, it could be used as a part of a series of steps such as\n Note that when the exit status is non-zero, NEWTREE in this sequence\n will contain a lot more output than just a tree.\n \n+git-merge-tree was written to provide users with the same information\n+that they'd have access to if using `git merge`:\n+  * what would be written to the working tree (the <OID of toplevel tree>)\n+  * the higher order stages that would be written to the index (the\n+    <Conflicted file info>)\n+  * any messages that would have been printed to stdout (the <Informational\n+    messages>)\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex cb4169d2271..1d4d6637b90 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -400,6 +400,7 @@ enum mode {\n struct merge_tree_options {\n \tint mode;\n \tint show_messages;\n+\tint name_only;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -456,7 +457,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->name_only)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -491,6 +496,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F(0, \"name-only\",\n+\t\t\t   &o.name_only,\n+\t\t\t   N_(\"list filenames without modes/oids/stages\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 8e6dba44288..0ec5f0d3f7e 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -65,6 +65,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(git rev-parse AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -108,7 +109,7 @@ anonymize_hash() {\n }\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \t# Expected results:\n@@ -143,7 +144,7 @@ do\n done\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -151,4 +152,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"448895","messageId":"9b65e743-729f-6449-b7ef-c8c9fb130221@web.de","threadId":"57288","inReplyTo":"7994775a9341b256d1ea7dfc417bb577d9a3195f.1645340082.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 04/12] merge-tree: implement real merges","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-02-20T09:03:22Z","receivedAt":"2022-02-20T09:03:41Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 20.02.22 um 07:54 schrieb Elijah Newren via GitGitGadget:\n> From: Elijah Newren <newren@gmail.com>\n>\n> This adds the ability to perform real merges rather than just trivial\n> merges (meaning handling three way content merges, recursive ancestor\n> consolidation, renames, proper directory/file conflict handling, and so\n> forth).  However, unlike `git merge`, the working tree and index are\n> left alone and no branch is updated.\n>\n> The only output is:\n>   - the toplevel resulting tree printed on stdout\n>   - exit status of 0 (clean), 1 (conflicts present), anything else\n>     (merge could not be performed; unknown if clean or conflicted)\n>\n> This output is meant to be used by some higher level script, perhaps in\n> a sequence of steps like this:\n>\n>    NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n>    test $? -eq 0 || die \"There were conflicts...\"\n>    NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n>    git update-ref $BRANCH1 $NEWCOMMIT\n>\n> Note that higher level scripts may also want to access the\n> conflict/warning messages normally output during a merge, or have quick\n> access to a list of files with conflicts.  That is not available in this\n> preliminary implementation, but subsequent commits will add that\n> ability (meaning that NEWTREE would be a lot more than a tree in the\n> case of conflicts).\n>\n> This also marks the traditional trivial merge of merge-tree as\n> deprecated.  The trivial merge not only had limited applicability, the\n> output format was also difficult to work with (and its format\n> undocumented), and will generally be less performant than real merges.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  Documentation/git-merge-tree.txt |  79 +++++++++++++++++++----\n>  builtin/merge-tree.c             |  44 ++++++++++++-\n>  t/t4301-merge-tree-write-tree.sh | 106 +++++++++++++++++++++++++++++++\n>  3 files changed, 216 insertions(+), 13 deletions(-)\n>  create mode 100755 t/t4301-merge-tree-write-tree.sh\n>\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index 58731c19422..589a83738ce 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -3,26 +3,81 @@ git-merge-tree(1)\n>\n>  NAME\n>  ----\n> -git-merge-tree - Show three-way merge without touching index\n> +git-merge-tree - Perform merge without touching index or working tree\n>\n>\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git merge-tree' <base-tree> <branch1> <branch2>\n> +'git merge-tree' [--write-tree] <branch1> <branch2>\n> +'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n>\n>  DESCRIPTION\n>  -----------\n> -Reads three tree-ish, and output trivial merge results and\n> -conflicting stages to the standard output.  This is similar to\n> -what three-way 'git read-tree -m' does, but instead of storing the\n> -results in the index, the command outputs the entries to the\n> -standard output.\n> -\n> -This is meant to be used by higher level scripts to compute\n> -merge results outside of the index, and stuff the results back into the\n> -index.  For this reason, the output from the command omits\n> -entries that match the <branch1> tree.\n> +\n> +Performs a merge, but does not make any new commits and does not read\n> +from or write to either the working tree or index.\n> +\n> +The first form will merge the two branches, doing a real merge.  A real\n> +merge is distinguished from a trivial merge in that it includes:\n> +\n> +  * three way content merges of individual files\n> +  * rename detection\n> +  * proper directory/file conflict handling\n> +  * recursive ancestor consolidation (i.e. when there is more than one\n> +    merge base, creating a virtual merge base by merging the merge bases)\n> +  * etc.\n> +\n> +After the merge completes, the first form will create a new toplevel\n> +tree object.  See `OUTPUT` below for details.\n> +\n> +The second form is deprecated; it is kept for backward compatibility\n> +reasons but may be deleted in the future.  Other than the optional\n> +`--trivial-merge`, it accepts no options.  It can only do a trivial\n> +merge.  It reads three tree-ish, and outputs trivial merge results and\n> +conflicting stages to the standard output in a semi-diff format.\n> +Since this was designed for higher level scripts to consume and merge\n> +the results back into the index, it omits entries that match\n> +<branch1>.  The result of this second form is similar to what\n> +three-way 'git read-tree -m' does, but instead of storing the results\n> +in the index, the command outputs the entries to the standard output.\n> +This form not only has limited applicability, the output format is\n> +also difficult to work with, and it will generally be less performant\n> +than the first form even on successful merges (especially if working\n> +in large repositories).  The remainder of this manual will only\n> +discuss the first form.\n> +\n> +OUTPUT\n> +------\n> +\n> +For either a successful or conflicted merge, the output from\n> +git-merge-tree is simply one line:\n> +\n> +\t<OID of toplevel tree>\n> +\n> +The printed tree object corresponds to what would be checked out in\n> +the working tree at the end of `git merge`, and thus may have files\n> +with conflict markers in them.\n> +\n> +EXIT STATUS\n> +-----------\n> +\n> +For a successful, non-conflicted merge, the exit status is 0.  When the\n> +merge has conflicts, the exit status is 1.  If the merge is not able to\n> +complete (or start) due to some kind of error, the exit status is\n> +something other than 0 or 1 (and the output is unspecified).\n> +\n> +USAGE NOTES\n> +-----------\n> +\n> +git-merge-tree was written to be low-level plumbing, similar to\n> +hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n> +Thus, it could be used as a part of a series of steps such as\n> +\n> +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n> +       test $? -eq 0 || die \"There were conflicts...\"\n> +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n> +       git update-ref $BRANCH1 $NEWCOMMIT\n>\n>  GIT\n>  ---\n> diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\n> index 0f9d928e862..af445cb1576 100644\n> --- a/builtin/merge-tree.c\n> +++ b/builtin/merge-tree.c\n> @@ -2,6 +2,9 @@\n>  #include \"builtin.h\"\n>  #include \"tree-walk.h\"\n>  #include \"xdiff-interface.h\"\n> +#include \"help.h\"\n> +#include \"commit-reach.h\"\n> +#include \"merge-ort.h\"\n>  #include \"object-store.h\"\n>  #include \"parse-options.h\"\n>  #include \"repository.h\"\n> @@ -398,7 +401,46 @@ struct merge_tree_options {\n>  static int real_merge(struct merge_tree_options *o,\n>  \t\t      const char *branch1, const char *branch2)\n>  {\n> -\tdie(_(\"real merges are not yet implemented\"));\n> +\tstruct commit *parent1, *parent2;\n> +\tstruct commit_list *common;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tstruct commit_list *j;\n> +\tstruct merge_options opt;\n> +\tstruct merge_result result = { 0 };\n> +\n> +\tparent1 = get_merge_parent(branch1);\n> +\tif (!parent1)\n> +\t\thelp_unknown_ref(branch1, \"merge-tree\",\n> +\t\t\t\t _(\"not something we can merge\"));\n> +\n> +\tparent2 = get_merge_parent(branch2);\n> +\tif (!parent2)\n> +\t\thelp_unknown_ref(branch2, \"merge-tree\",\n> +\t\t\t\t _(\"not something we can merge\"));\n> +\n> +\tinit_merge_options(&opt, the_repository);\n> +\n> +\topt.show_rename_progress = 0;\n> +\n> +\topt.branch1 = branch1;\n> +\topt.branch2 = branch2;\n> +\n> +\t/*\n> +\t * Get the merge bases, in reverse order; see comment above\n> +\t * merge_incore_recursive in merge-ort.h\n> +\t */\n> +\tcommon = get_merge_bases(parent1, parent2);\n> +\tif (!common)\n> +\t\tdie(_(\"refusing to merge unrelated histories\"));\n> +\tfor (j = common; j; j = j->next)\n> +\t\tcommit_list_insert(j->item, &merge_bases);\n\nThis loop creates a reversed copy of \"common\".  You could use\nreverse_commit_list() instead to do it in-place and avoid the\nallocations.  Only the copy, \"merge_bases\", is used below.\n\n> +\n> +\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n> +\tif (result.clean < 0)\n> +\t\tdie(_(\"failure to merge\"));\n> +\tputs(oid_to_hex(&result.tree->object.oid));\n> +\tmerge_finalize(&opt, &result);\n> +\treturn !result.clean; /* result.clean < 0 handled above */\n>  }\n>\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\n> new file mode 100755\n> index 00000000000..6d321652e21\n> --- /dev/null\n> +++ b/t/t4301-merge-tree-write-tree.sh\n> @@ -0,0 +1,106 @@\n> +#!/bin/sh\n> +\n> +test_description='git merge-tree --write-tree'\n> +\n> +. ./test-lib.sh\n> +\n> +# This test is ort-specific\n> +if test \"$GIT_TEST_MERGE_ALGORITHM\" != \"ort\"\n> +then\n> +\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n> +\ttest_done\n> +fi\n> +\n> +test_expect_success setup '\n> +\ttest_write_lines 1 2 3 4 5 >numbers &&\n> +\techo hello >greeting &&\n> +\techo foo >whatever &&\n> +\tgit add numbers greeting whatever &&\n> +\ttest_tick &&\n> +\tgit commit -m initial &&\n> +\n> +\tgit branch side1 &&\n> +\tgit branch side2 &&\n> +\tgit branch side3 &&\n> +\n> +\tgit checkout side1 &&\n> +\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n> +\techo hi >greeting &&\n> +\techo bar >whatever &&\n> +\tgit add numbers greeting whatever &&\n> +\ttest_tick &&\n> +\tgit commit -m modify-stuff &&\n> +\n> +\tgit checkout side2 &&\n> +\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n> +\techo yo >greeting &&\n> +\tgit rm whatever &&\n> +\tmkdir whatever &&\n> +\t>whatever/empty &&\n> +\tgit add numbers greeting whatever/empty &&\n> +\ttest_tick &&\n> +\tgit commit -m other-modifications &&\n> +\n> +\tgit checkout side3 &&\n> +\tgit mv numbers sequence &&\n> +\ttest_tick &&\n> +\tgit commit -m rename-numbers\n> +'\n> +\n> +test_expect_success 'Clean merge' '\n> +\tTREE_OID=$(git merge-tree --write-tree side1 side3) &&\n> +\tq_to_tab <<-EOF >expect &&\n> +\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n> +\t100644 blob $(git rev-parse side1:numbers)Qsequence\n> +\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n> +\tEOF\n> +\n> +\tgit ls-tree $TREE_OID >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'Content merge and a few conflicts' '\n> +\tgit checkout side1^0 &&\n> +\ttest_must_fail git merge side2 &&\n> +\texpected_tree=$(git rev-parse AUTO_MERGE) &&\n> +\n> +\t# We will redo the merge, while we are still in a conflicted state!\n> +\ttest_when_finished \"git reset --hard\" &&\n> +\n> +\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n> +\tactual_tree=$(head -n 1 RESULT) &&\n> +\n> +\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n> +\t# exactly match.  Dig into individual files.\n> +\n> +\t# Numbers should have three-way merged cleanly\n> +\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n> +\tgit show ${actual_tree}:numbers >actual &&\n> +\ttest_cmp expect actual &&\n> +\n> +\t# whatever and whatever~<branch> should have same HASHES\n> +\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n> +\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n> +\ttest_cmp expect actual &&\n> +\n> +\t# greeting should have a merge conflict\n> +\tgit show ${expected_tree}:greeting >tmp &&\n> +\tsed -e s/HEAD/side1/ tmp >expect &&\n> +\tgit show ${actual_tree}:greeting >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n> +\t# Mis-spell with single \"s\" instead of double \"s\"\n> +\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n> +\n> +\tgrep \"error: unknown option.*mesages\" expect\n> +'\n> +\n> +test_expect_success 'Barf on too many arguments' '\n> +\ttest_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&\n> +\n> +\tgrep \"^usage: git merge-tree\" expect\n> +'\n> +\n> +test_done\n"},{"id":"448897","messageId":"220220.86k0dpd8c8.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-20T10:23:41Z","receivedAt":"2022-02-20T12:35:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sun, Feb 20 2022, Elijah Newren via GitGitGadget wrote:\n\n> == Updates Log ==\n>\n> Many thanks to the many reviewers who provided good feedback on the most\n> recent round -- Junio, Ævar, Josh, Emily, and perhaps some others I've\n> forgotten from review club.\n>\n> Updates since v4:\n>\n>  * Fixed double \"is\" in documentation.\n>  * Fixed a few small items with testcases\n>\n> Updates since v3 (or v5, if you include the rounds at\n> https://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/):\n>\n>  * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n>    being used and opened a can of worms[1]\n>  * [Patch 3] Restructured argument checking, including using an enum\n>  * [Patch 4] Restored the extended paragraph about the deprecated form of\n>    git-merge-tree, mentioned write-tree in plumbing commands, and a few\n>    other small fixups to the documentation\n>  * [Patch 4] Also provide an example of a clean merge rather than just a\n>    conflicted one\n>  * [Patch 6] Fix the incompatible arguments check and add some tests for it\n>  * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n>    easier to read (less repeated sed)\n>  * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n>    option for now\n>  * [Patch 10] When -z passed, the tree in the first section should have a\n>    trailing NUL rather than trailing newline [1]\n>    https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n>\n> Stuff NOT included that reviewers brought up in earlier rounds:\n>\n>  * Very generic (mode, oid, stage, filename) printing formatting[2]\n>  * Always printing 3 stages for each filename with conflicts[3]\n>  * Attempting to group conflict stages by logical conflict rather than by\n>    affected target filepath[4]\n>  * Providing similar functionality for doing cherry-picks/rebases/reverts,\n>    i.e. a scheme for three-way merges with a specified merge-base[5]. That's\n>    being deferred to a future series. [2]\n>    https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n>    [3]\n>    https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n>    [4]\n>    https://lore.kernel.org/git/CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com/\n>    [5]\n>    https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n\nI've looked through this, I think it all looks good overall & that the\nthings that needed to be addressed (as opposed to my --format rambling)\nhave been.\n\nI think all the code should be ready for \"next\".\n\nI suggested (I think around getopts discussion) in an earlier that the\ncode would have been easier with a new built-in, but if we're\ndeprecating the existing \"mode\" I think using the name is probably\nbetter in the end.\n\nI find the resulting documentation to be really hard to grok though\nbecause we're effectively describing two different commands. The current\ndocs are small: https://git-scm.com/docs/git-merge-tree\n\nI built the tip of this series and read the manpage, and found myself\nneeding to carefully squint to see what referred to what mode in the\ndocs.\n\nE.g. by the time it's discussing \"-z\" and other options the reader needs\nto be astute really aware of the context, and infer from the lack of\n\"<options>\" on the \"--trivial-merge\" that these options refer to the\n\"--write-tree\" only.\n\nThe same goes for the rest of \"--trivial-merge\". I.e. I found myself\nneeding to read the whole docs word-by-word (no skimming!) to see if\nOUTPUT etc. was going to describe its output, or just the \"new\" mode.\n\nIt is my NSHO that man pages should be structured for the impatient\nreader :)\n\nThen when you say \"git-merge-tree was written to be[...]\" I thought \"ah\nha! surely this will discuss the since-2005 implemented mode\", but \"was\nwritten to be\" is referring to code new in this series.\n\nThe below patch-on-top addresses all those concerns. Basically I just\nadded a line to the top of the DESCRIPTION saying that you should read a\n\"DEPRECATED DESCRIPTION\" section at the end for \"--trivial-merge\", and\nthat all of the rest is talking about the \"--write-tree\" mode.\n\nI then edited various prose to do away with the now-unnecessary \"the\nfirst form\" etc.\n\nThis diff is better against \"master\" in that you'll see that the current\nmerge-tree DESCRIPTION section isn't touched at all (it's now just under\na new heading), but this diff is against the tip of your series.\n\nThere's various other small fixes while I was it it here, e.g. all your\ncross-section links were using some pseudo-not-quite-ASCIIDOC syntax\nthat doesn't work. Now it uses the right syntax. Ditto link-ifying\nreferences to \"mktag\" etc.\n\nI don't know if you'd consider this for a v6, or if I should just submit\nthis on top myself, but in any case here it is. I'll leave it to you how\nyou'd like to proceed with it:\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 306149fa0e2..723b1995426 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,17 +9,24 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n-'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n+'git merge-tree' --write-tree [<options>] <branch1> <branch2>\n+'git merge-tree' --trivial-merge <base-tree> <branch1> <branch2>\n \n+[[NEWMERGE]]\n DESCRIPTION\n -----------\n \n-Performs a merge, but does not make any new commits and does not read\n-from or write to either the working tree or index.\n+This command has a modern `--write-tree` mode and a deprecated\n+`--trivial-merge` mode. The rest of this documentation describes\n+modern `--write-tree` mode unless otherwise specified. see\n+<<DEPMERGE,DEPRECATED DESCRIPTION>> below for a summary of the\n+`--trivial-merge` mode.\n \n-The first form will merge the two branches, doing a real merge.  A real\n-merge is distinguished from a trivial merge in that it includes:\n+Performs a \"real\" merge, but does not make any new commits and does\n+not read from or write to either the working tree or index.\n+\n+The performed merge will use the same feature as the \"real\"\n+linkgit:git-merge[1], including:\n \n   * three way content merges of individual files\n   * rename detection\n@@ -28,24 +35,8 @@ merge is distinguished from a trivial merge in that it includes:\n     merge base, creating a virtual merge base by merging the merge bases)\n   * etc.\n \n-After the merge completes, the first form will create a new toplevel\n-tree object.  See `OUTPUT` below for details.\n-\n-The second form is deprecated; it is kept for backward compatibility\n-reasons but may be deleted in the future.  Other than the optional\n-`--trivial-merge`, it accepts no options.  It can only do a trivial\n-merge.  It reads three tree-ish, and outputs trivial merge results and\n-conflicting stages to the standard output in a semi-diff format.\n-Since this was designed for higher level scripts to consume and merge\n-the results back into the index, it omits entries that match\n-<branch1>.  The result of this second form is similar to what\n-three-way 'git read-tree -m' does, but instead of storing the results\n-in the index, the command outputs the entries to the standard output.\n-This form not only has limited applicability, the output format is\n-also difficult to work with, and it will generally be less performant\n-than the first form even on successful merges (especially if working\n-in large repositories).  The remainder of this manual will only\n-discuss the first form.\n+After the merge completes, a newtoplevel tree object is created.  See\n+`OUTPUT` below for details.\n \n OPTIONS\n -------\n@@ -54,7 +45,7 @@ OPTIONS\n \tDo not quote filenames in the <Conflicted file info> section,\n \tand end each filename with a NUL character rather than\n \tnewline.  Also begin the messages section with a NUL character\n-\tinstead of a newline.  See OUTPUT below for more information.\n+\tinstead of a newline.  See <<OUTPUT>> below for more information.\n \n --name-only::\n \tIn the Conflicted file info section, instead of writing a list\n@@ -74,11 +65,12 @@ OPTIONS\n \tshare no common history.  This flag can be given to override that\n \tcheck and make the merge proceed anyway.\n \n+[[OUTPUT]]\n OUTPUT\n ------\n \n-By default, for a successful merge, the output from git-merge-tree is\n-simply one line:\n+For a successful merge, the output from git-merge-tree is simply one\n+line:\n \n \t<OID of toplevel tree>\n \n@@ -90,6 +82,7 @@ Whereas for a conflicted merge, the output is by default of the form:\n \n These are discussed individually below.\n \n+[[OIDTLT]]\n OID of toplevel tree\n ~~~~~~~~~~~~~~~~~~~~\n \n@@ -98,6 +91,7 @@ working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.  This section\n is always followed by a newline (or NUL if `-z` is passed).\n \n+[[CFI]]\n Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n@@ -111,6 +105,7 @@ the `--name-only` option is passed, the mode, object, and stage will\n be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n character instead of a newline character.\n \n+[[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n@@ -138,72 +133,94 @@ something other than 0 or 1 (and the output is unspecified).\n USAGE NOTES\n -----------\n \n-git-merge-tree was written to be low-level plumbing, similar to\n-hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n-Thus, it could be used as a part of a series of steps such as\n+This command is intended as low-level plumbing, similar to\n+linkgit:git-hash-object[1], linkgit:git-mktree[1],\n+linkgit:git-commit-tree[1], linkgit:git-write-tree[1],\n+linkgit:git-update-ref[1], and linkgit:git-mktag[1].  Thus, it can be\n+used as a part of a series of steps such as:\n \n        NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n        test $? -eq 0 || die \"There were conflicts...\"\n        NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n        git update-ref $BRANCH1 $NEWCOMMIT\n \n-Note that when the exit status is non-zero, NEWTREE in this sequence\n+Note that when the exit status is non-zero, `NEWTREE` in this sequence\n will contain a lot more output than just a tree.\n \n-git-merge-tree was written to provide users with the same information\n-that they'd have access to if using `git merge`:\n-  * what would be written to the working tree (the <OID of toplevel tree>)\n+The output will include the same information that you'd get with\n+linkgit:git-merge[1]:\n+\n+  * what would be written to the working tree (the <<OIDTLT,OID of toplevel tree>>)\n   * the higher order stages that would be written to the index (the\n-    <Conflicted file info>)\n-  * any messages that would have been printed to stdout (the <Informational\n-    messages>)\n+    <<CFI,Conflicted file info>>)\n+  * any messages that would have been printed to stdout (the <<IM,Informational\n+    messages>>)\n \n MISTAKES TO AVOID\n -----------------\n \n Do NOT look through the resulting toplevel tree to try to find which\n-files conflict; parse the <Conflicted file info> section instead.  Not\n+files conflict; parse the <<CFI,Conflicted file info>> section instead.  Not\n only would parsing an entire tree be horrendously slow in large\n repositories, there are numerous types of conflicts not representable by\n conflict markers (modify/delete, mode conflict, binary file changed on\n both sides, file/directory conflicts, various rename conflict\n permutations, etc.)\n \n-Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n+Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean merge;\n check the exit status.  A merge can have conflicts without having\n individual files conflict (there are a few types of directory rename\n conflicts that fall into this category, and others might also be added\n in the future).\n \n Do NOT attempt to guess or make the user guess the conflict types from\n-the <Conflicted file info> list.  The information there is insufficient\n+the <<CFI,Conflicted file info>> list.  The information there is insufficient\n to do so.  For example: Rename/rename(1to2) conflicts (both sides\n renamed the same file differently) will result in three different file\n having higher order stages (but each only has one higher order stage),\n-with no way (short of the <Informational messages> section) to determine\n+with no way (short of the <<IM,Informational messages>> section) to determine\n which three files are related.  File/directory conflicts also result in\n a file with exactly one higher order stage.\n Possibly-involved-in-directory-rename conflicts (when\n \"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n a file with exactly one higher order stage.  In all cases, the\n-<Informational messages> section has the necessary info, though it is\n+<<IM,Informational messages>> section has the necessary info, though it is\n not designed to be machine parseable.\n \n-Do NOT assume all filenames listed in the <Informational messages>\n+Do NOT assume all filenames listed in the <<IM,Informational messages>>\n section had conflicts.  Messages can be included for files that have no\n conflicts, such as \"Auto-merging <file>\".\n \n-AVOID taking the OIDS from the <Conflicted file info> and re-merging\n+AVOID taking the OIDS from the <<CFI,Conflicted file info>> and re-merging\n them to present the conflicts to the user.  This will lose information.\n-Instead, look up the version of the file found within the <OID of\n-toplevel tree> and show that instead.  In particular, the latter will\n+Instead, look up the version of the file found within the <<OIDTLT,OID of\n+toplevel tree>> and show that instead.  In particular, the latter will\n have conflict markers annotated with the original branch/commit being\n merged and, if renames were involved, the original filename.  While you\n could include the original branch/commit in the conflict marker\n annotations when re-merging, the original filename is not available from\n-the <Conflicted file info> and thus you would be losing information that\n+the <<CFI,Conflicted file info>> and thus you would be losing information that\n might help the user resolve the conflict.\n \n+[[DEPMERGE]]\n+DEPRECATED DESCRIPTION\n+----------------------\n+\n+Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this\n+documentation this section describes describes the deprecated\n+`--trivial-merge` mode.\n+\n+Reads three tree-ish, and output trivial merge results and\n+conflicting stages to the standard output.  This is similar to\n+what three-way 'git read-tree -m' does, but instead of storing the\n+results in the index, the command outputs the entries to the\n+standard output.\n+\n+This is meant to be used by higher level scripts to compute\n+merge results outside of the index, and stuff the results back into the\n+index.  For this reason, the output from the command omits\n+entries that match the <branch1> tree.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\n\n"},{"id":"448958","messageId":"nycvar.QRO.7.76.6.2202210932070.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BF+LNPFcyj-Ao2nFJw=a9OeX6B-rpbRrn56vOi=h6b9iw@mail.gmail.com","subject":"Re: [PATCH v2 04/13] merge-tree: implement real merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T08:40:24Z","receivedAt":"2022-02-21T08:40:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 2 Feb 2022, Elijah Newren wrote:\n\n> On Wed, Feb 2, 2022 at 1:30 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> > > +Performs a merge, but does not make any new commits and does not read\n> > > +from or write to either the working tree or index.\n> > > +\n> > > +The second form is deprecated and supported only for backward\n> > > +compatibility.  It will likely be removed in the future, and will not\n> > > +be discussed further in this manual.\n> >\n> > This, especially the deletion of the original description on what\n> > trivial merge does, may be premature, especially if it is still\n> > \"supported for backward compatibility\".\n>\n> I actually extended it, but Dscho suggested removing it entirely --\n> https://lore.kernel.org/git/nycvar.QRO.7.76.6.2201251804250.2121@tvgsbejvaqbjf.bet/.\n> I can restore it; does that paragraph look good to you (you can see\n> the full thing even if it's split by Dscho's commentary).\n\nI do not necessarily feel strong about keeping the paragraph, but I would\nappreciate a better rationale for that than \"because it was there\npreviously\".\n\nAt this point there cannot be any doubt that the \"trivial mode\" is worth\ndeprecating and getting rid off because it has been in little or no use.\n\nI mean, elsewhere we are overzealously removing stuff \"because _core Git_\ndoes not use it\", and here we keep something for... reasons?\n\n> > > +test_expect_success 'Content merge and a few conflicts' '\n> > > +     git checkout side1^0 &&\n> > > +     test_must_fail git merge side2 &&\n> > > +     expected_tree=$(cat .git/AUTO_MERGE) &&\n> > > +\n> > > +     # We will redo the merge, while we are still in a conflicted state!\n> > > +     test_when_finished \"git reset --hard\" &&\n> > > +\n> > > +     test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n> > > +     actual_tree=$(head -n 1 RESULT) &&\n> > > +\n> > > +     # Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n> > > +     # exactly match.  Dig into individual files.\n> > > +\n> > > +     # Numbers should have three-way merged cleanly\n> > > +     test_write_lines 0 1 2 3 4 5 6 >expect &&\n> > > +     git show ${actual_tree}:numbers >actual &&\n> > > +     test_cmp expect actual &&\n> > > +\n> > > +     # whatever and whatever~<branch> should have same HASHES\n> > > +     git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n> > > +     git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n> > > +     test_cmp expect actual &&\n> > > +\n> > > +     # greeting should have a merge conflict\n> > > +     git show ${expected_tree}:greeting >tmp &&\n> > > +     cat tmp | sed -e s/HEAD/side1/ >expect &&\n> > > +     git show ${actual_tree}:greeting >actual &&\n> > > +     test_cmp expect actual\n> > > +'\n> >\n> > It is somewhat sad that we need to reivent merge test cases over and\n> > over, instead of easily reuse an existing one by replacing\n> >\n> >         git checkout one &&\n> >         git merge two\n> >\n> > with\n> >\n> >         git checkout one &&\n> >         T=$(git merge-tree HEAD two) &&\n> >         C=$(git commit-tree $T -p HEAD -p two) &&\n> >         git reset --hard $C\n> >\n> > ;-)\n>\n> Sorry...I'm afraid I'm not following.\n\nI cannot speak for Junio (nor do I want to). Having said that, I interpret\nthe quoted section as a lament on the somewhat repetitive complexity that\nis required by our tests merely to set up a scenario where we can run the\nactual command we want to test.\n\nIf my interpretation is correct, then it is probably fair to suggest using\n`test_merge` in a setup-type of test case (because that not only runs `git\nmerge` but also tags the result), and then use `git reset --hard <tag>` in\nsubsequent test cases instead of re-running the `checkout && merge` dance.\n\nCiao,\nDscho\n"},{"id":"448961","messageId":"nycvar.QRO.7.76.6.2202210956430.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BH_TiJaDpn2+VVjCb83NEFjL9teSk06+YiZyFGiTu8Lpg@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T09:06:13Z","receivedAt":"2022-02-21T09:14:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 3 Feb 2022, Elijah Newren wrote:\n\n> On Thu, Feb 3, 2022 at 2:42 AM Johannes Altmanninger <aclopte@gmail.com> wrote:\n> >\n> > On Wed, Feb 02, 2022 at 04:18:39PM -0800, Elijah Newren wrote:\n> > > On Wed, Feb 2, 2022 at 2:01 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > > >\n> > > > Elijah Newren <newren@gmail.com> writes:\n> > > >\n> > > > > Yes, you are reading right.  I think the cherry-pick/rebase\n> > > > > replacement actually deserves a separate command from what merges\n> > > > > should use; replaying a sequence of commits just has a number of UI\n> > > > > differences and abilities that I think pull it in a different\n> > > > > direction.\n> > > >\n> > > > I completely disagree.  Each individual step in a sequence of\n> > > > replaying commits in order (or in reverse order) should be\n> > > > scriptable as a single merge-tree that takes \"apply the change to go\n> > > > from A^ to A on X\".  Sequencing and placing UI around it is a job\n> > > > for the script that drives merge-tree.\n> > >\n> > > Adding such an ability to merge-tree would be trivial -- it basically\n> > > involves just two things: (1) accepting one extra argument, and (2)\n> > > calling merge_incore_nonrecursive() instead of\n> > > merge_incore_recursive().\n> > >\n> > > However, I think forking a subprocess for every merge of a series of\n> > > commits is a completely unreasonable overhead, so even if we provide\n> > > such an option to merge-tree, I still want a separate plumbing-ish\n> > > tool that does non-worktree/non-index replaying of commits which is\n> > > not written as a driver of merge-tree.  That other tool should just\n> > > call merge_incore_nonrecursive() directly.  And such a tool, since it\n> > > should handle an arbitrary number of commits, should certainly be able\n> > > to handle just one commit.  From that angle, it feels like adding\n> > > another mode to merge-tree would just be a partial duplication of the\n> > > other tool.\n> >\n> > I wonder how the UI of a tool that does non-worktree/non-index cherry-picks\n> > will look like.  I'd expect it to produce the same output as merge-tree,\n> > except cherry-pick should probably output a commit OID, not a tree.\n> >\n> > Maybe we want a unified command that produces commits from any sequence of\n> > merge/cherry-pick/revert/reword steps. The obvious UI would use something\n> > like the rebase-todo list as input.  For example:\n> >\n> >         $ echo '\n> >         pick commit1\n> >         reword commit2  # edit commit message in $GIT_EDITOR\n> >         merge commit3 -m \"log message\"\n> >         ' | git create-commit commit0\n> >         <OID of final commit>\n> >\n> > we start from commit0 and apply steps one-by-one. Obviously, one unsolved\n> > problem is how to pass parameters like commit messages if no editor should\n> > be invoked (my sketch uses -m).\n> > If any of the steps fails when merging merge, then we get the tree with\n> > conflicts\n> >\n> >         $ echo '\n> >         pick commit1\n> >         pick commit2\n> >         pick commit-that-does-not-apply\n> >         ' | git create-commit commit0\n> >         <OID of commit after step 2>\n> >         <OID of toplevel tree after failed merge>\n> >         <Conflicted file info>\n> >         <Informational messages>\n> >\n> > Replaying a series of commits might look like this:\n> >\n> >         $ echo 'pick commit1 ^commit0' | git create-commit new-base\n> >\n> > I'm concluding that this is a difficult UI problem\n>\n> I agree.  I've got a lot of thoughts on it, and some work in progress\n> towards it (https://github.com/newren/git/tree/replay -- _very_ hacky,\n> not even close to alpha quality, lots of fixup commits, todo comments,\n> random brain dump files added to the tree, based on a previous round\n> of this patch series, not updated for weeks, etc., etc.)\n\nJust chiming in that I find that very exciting. But it's a tangent, and\nslightly distracting from the topic at hand, so I would like to ask to\nfocus back on server-side merges.\n\n> > and having a merge-tree command that accepts a \"common ancestor\"\n> > parameter could make it easier to experiment.  Of course that depends\n> > on who is experimenting.\n>\n> I think that would result in experiments and eventually full-blown\n> scripts designed around forking subprocesses for every merge, and\n> pushes us back into the world of having a scripted-rebase again.  Yes,\n> I know people can transliterate shell back to C; it seems to always be\n> done as a half-way measure with the forking just being done from C or\n> have other UI-warts guided by the shell design.  In fact, *that* was\n> the primary reason for me not providing a merge-tree option based on\n> merge_incore_nonrecursive(), despite how trivial it'd be to provide\n> it.  If someone wanted a merge_incore_nonrecursive() mode for\n> merge-tree for reasons other than attempting to build a\n> rebase/cherry-pick replacement based on it, then I'd be much happier\n> to provide it.\n>\n> If someone wants to experiment with what a plumbing-ish\n> rebase/cherry-pick would look like, the _right_ way to do it would be\n> making using of merge_incore_nonrecursive() directly.  If they want\n> example code, I already provided some a year and a half ago and got it\n> merged into git.git in the form of t/helper/test-fast-rebase.c.  My\n> \"replay\" branch is based on that code, but (a) moves it from t/helper\n> to a real builtin, (b) removes the hardcoded very strict input, (c)\n> removes the line of code doing the index & working tree updates, and\n> (d) modifies the output to be a more plumbing-ish style.\n\nI actually implemented that so I could provide apples-to-apples\nspeed comparisons between libgit2 and merge-ort:\n\n-- snip --\nFrom 6a865c691810b67dc15ddb57ad110bd6fdfc2f12 Mon Sep 17 00:00:00 2001\nFrom: Johannes Schindelin <johannes.schindelin@gmx.de>\nDate: Fri, 28 Jan 2022 23:28:20 +0100\nSubject: [PATCH] merge-tree: optionally force a simple 3-way merge\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-tree.c | 72 ++++++++++++++++++++++++++++++--------------\n 1 file changed, 50 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 58c0ddc5a3..1007aaaede 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -396,6 +396,7 @@ struct merge_tree_options {\n \tint allow_unrelated_histories;\n \tint show_messages;\n \tint exclude_modes_oids_stages;\n+\tconst char *nonrecursive_base;\n };\n\n static int real_merge(struct merge_tree_options *o,\n@@ -409,34 +410,58 @@ static int real_merge(struct merge_tree_options *o,\n \tstruct merge_options opt;\n \tstruct merge_result result = { 0 };\n\n-\tparent1 = get_merge_parent(branch1);\n-\tif (!parent1)\n-\t\thelp_unknown_ref(branch1, \"merge-tree\",\n-\t\t\t\t _(\"not something we can merge\"));\n-\n-\tparent2 = get_merge_parent(branch2);\n-\tif (!parent2)\n-\t\thelp_unknown_ref(branch2, \"merge-tree\",\n-\t\t\t\t _(\"not something we can merge\"));\n-\n \tinit_merge_options(&opt, the_repository);\n\n \topt.show_rename_progress = 0;\n\n-\topt.branch1 = branch1;\n-\topt.branch2 = branch2;\n+\tif (o->nonrecursive_base) {\n+\t\tstruct object_id base_oid, head_oid, merge_oid;\n+\t\tstruct tree *base_tree, *head_tree, *merge_tree;\n+\n+\t\topt.ancestor = \"(base)\";\n+\t\topt.branch1 = \"(branch1)\";\n+\t\topt.branch2 = \"(branch2)\";\n+\n+\t\tif (get_oid_treeish(o->nonrecursive_base, &base_oid))\n+\t\t\tdie(\"could not parse base '%s'\", o->nonrecursive_base);\n+\t\tbase_tree = parse_tree_indirect(&base_oid);\n+\t\tif (get_oid_treeish(branch1, &head_oid))\n+\t\t\tdie(\"could not parse head '%s'\", branch1);\n+\t\thead_tree = parse_tree_indirect(&head_oid);\n+\t\tif (get_oid_treeish(branch2, &merge_oid))\n+\t\t\tdie(\"could not parse merge '%s'\", branch2);\n+\t\tmerge_tree = parse_tree_indirect(&merge_oid);\n+\n+\t\tmerge_incore_nonrecursive(&opt,\n+\t\t\t\t\t  base_tree, head_tree, merge_tree,\n+\t\t\t\t\t  &result);\n+\t} else {\n+\t\tparent1 = get_merge_parent(branch1);\n+\t\tif (!parent1)\n+\t\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t\t _(\"not something we can merge\"));\n+\n+\t\tparent2 = get_merge_parent(branch2);\n+\t\tif (!parent2)\n+\t\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t\t _(\"not something we can merge\"));\n+\n+\t\topt.branch1 = branch1;\n+\t\topt.branch2 = branch2;\n\n-\t/*\n-\t * Get the merge bases, in reverse order; see comment above\n-\t * merge_incore_recursive in merge-ort.h\n-\t */\n-\tcommon = get_merge_bases(parent1, parent2);\n-\tif (!common && !o->allow_unrelated_histories)\n-\t\tdie(_(\"refusing to merge unrelated histories\"));\n-\tfor (j = common; j; j = j->next)\n-\t\tcommit_list_insert(j->item, &merge_bases);\n+\t\t/*\n+\t\t * Get the merge bases, in reverse order; see comment above\n+\t\t * merge_incore_recursive in merge-ort.h\n+\t\t */\n+\t\tcommon = get_merge_bases(parent1, parent2);\n+\t\tif (!common && !o->allow_unrelated_histories)\n+\t\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\t\tfor (j = common; j; j = j->next)\n+\t\t\tcommit_list_insert(j->item, &merge_bases);\n+\n+\t\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\t}\n\n-\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n\n@@ -501,6 +526,9 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.allow_unrelated_histories,\n \t\t\t   N_(\"allow merging unrelated histories\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_STRING(0, \"force-non-recursive-base\", &o.nonrecursive_base,\n+\t\t\t   N_(\"base-tree\"),\n+\t\t\t   N_(\"force a simple three-way merge\")),\n \t\tOPT_END()\n \t};\n\n-- snap --\n\nI do strongly agree that this should _not_ enter core Git's code, I just\nprovide this in case someone else wants to play with merge-ort on the\nserver side in an existing code base.\n\n> We'll certainly have discussions on what that should look like.  But a\n> plumbing-ish replacement for merge was much simpler, and made sense to\n> do first.  I would prefer to concentrate on getting that hammered down\n> first.  Then I'll start discussions on a plumbing-ish\n> rebase/cherry-pick.  And if that doesn't fulfill all the needs that\n> folks think they want out of merge-tree, then we can add a\n> merge_incore_nonrecursive()-based mode to merge-tree.  It's all\n> coming, but having fought transliterations-of-scripts in\n> merge-recursive.c, sequencer.c, stash.c, rebase.c, etc. for years I\n> really, really don't want any more of that.  Let's end that insanity.\n\nBeing the driving force behind many a \"built-in-ification\" of scripted\ncommands, I wholeheartedly agree. You can still see the fall-out of\ndesigning commands in a scripted fashion, without any way to represent\ndata structures other than strings. I wish we had come up with a better\ndesign to prototype commands than to write shell scripts. But I have to\nadmit that even I do not have any better idea than to work on a proper API\nfor libgit.a (which has historically invariably seen push-back from\nJunio).\n\nWhile I agree that this discussion is a valuable one, right now I would\nlike to focus on getting the server-side merges done, and once that has\nhappened, move on to the replay/sequencer/API discussion (which will\nprobably be a big one, not so much for technical reasons but more for all\ntoo human ones).\n\nCiao,\nDscho\n"},{"id":"448962","messageId":"nycvar.QRO.7.76.6.2202211011510.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BFFcFxWL+FRSf9ANwHU1mp_oWcsfLOwvBAuv-J3oNh3SA@mail.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T09:13:09Z","receivedAt":"2022-02-21T09:33:39Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 3 Feb 2022, Elijah Newren wrote:\n\n> On Thu, Feb 3, 2022 at 8:24 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> >\n> > On Thu, Feb 03 2022, Elijah Newren wrote:\n> >\n> > > Man, what a can of worms this all is.  Maybe I really should just drop\n> > > patches 5, 6, and 8 for now...\n> >\n> > Yeah, I really think it's worth it to just sprinkle a tiny bit of\n> > if/else (or a macro) here and print to stderr inline or not. We can make\n> > some use of some usage.c when there's good reason to do so, but this bit\n> > just seems like a needless digression.\n> >\n> > I hope all of this has helped somewhat ...\n>\n> Absolutely; thanks for reviewing!  These parts may just end up in me\n> dropping some patches for now (since they're not actually being used\n> anyway), but I think it's all good feedback.\n\nSo we dropped some useful patches future-proofing `merge-tree` for the\nsake of appeasing a refactoring with no immediately obvious benefit? I\nreally don't like that direction.\n\nCiao,\nDscho\n"},{"id":"448963","messageId":"nycvar.QRO.7.76.6.2202211015360.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"220220.86k0dpd8c8.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T09:16:13Z","receivedAt":"2022-02-21T09:38:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 20 Feb 2022, Ævar Arnfjörð Bjarmason wrote:\n\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index 306149fa0e2..723b1995426 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -9,17 +9,24 @@ git-merge-tree - Perform merge without touching index or working tree\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n> -'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n> +'git merge-tree' --write-tree [<options>] <branch1> <branch2>\n> +'git merge-tree' --trivial-merge <base-tree> <branch1> <branch2>\n\nGiven that we want to get away from `--trivial-merge` (and probably even\ndeprecating and then dropping it), this direction makes no sense.\n\nCiao,\nJohannes\n"},{"id":"448965","messageId":"nycvar.QRO.7.76.6.2202211019490.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"9b65e743-729f-6449-b7ef-c8c9fb130221@web.de","subject":"Re: [PATCH v5 04/12] merge-tree: implement real merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T09:25:23Z","receivedAt":"2022-02-21T10:03:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 20 Feb 2022, René Scharfe wrote:\n\n> Am 20.02.22 um 07:54 schrieb Elijah Newren via GitGitGadget:\n>\n> > +\t/*\n> > +\t * Get the merge bases, in reverse order; see comment above\n> > +\t * merge_incore_recursive in merge-ort.h\n> > +\t */\n> > +\tcommon = get_merge_bases(parent1, parent2);\n> > +\tif (!common)\n> > +\t\tdie(_(\"refusing to merge unrelated histories\"));\n> > +\tfor (j = common; j; j = j->next)\n> > +\t\tcommit_list_insert(j->item, &merge_bases);\n>\n> This loop creates a reversed copy of \"common\".  You could use\n> reverse_commit_list() instead to do it in-place and avoid the\n> allocations.  Only the copy, \"merge_bases\", is used below.\n\nCurious. When I read this first, I immediately assumed this was\ncopy-pasted from `merge-recursive.c`, but it wasn't\n(https://github.com/git/git/blob/v2.35.1/merge-recursive.c#L3591-L3592):\n\n\t\tmerge_bases = get_merge_bases(h1, h2);\n\t\tmerge_bases = reverse_commit_list(merge_bases);\n\nI tried to figure out where the manual reversal might have been\ncopy-pasted from, but came up empty-handed. The comment in\nhttps://github.com/git/git/blob/v2.35.1/merge-ort.h#L40-L48 did not shed\nany light on it (but took me down memory lane, all the way to 2006!).\n\nCiao,\nDscho\n"},{"id":"448966","messageId":"nycvar.QRO.7.76.6.2202211029220.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BFyaakDSjHULpBRPQqq_jz2keyufHo1MjNS6dHQNR+JLQ@mail.gmail.com","subject":"Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T09:31:47Z","receivedAt":"2022-02-21T10:08:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\n[reinstating the Cc: list]\n\nOn Sat, 12 Feb 2022, Elijah Newren wrote:\n\n> On Fri, Feb 4, 2022 at 3:12 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Fri, 28 Jan 2022, Elijah Newren wrote:\n> >\n> > > On Fri, Jan 28, 2022 at 8:57 AM Johannes Schindelin\n> > > <Johannes.Schindelin@gmx.de> wrote:\n> > > >\n> > > > Hi Elijah,\n> > > >\n> > > > On Sat, 22 Jan 2022, Elijah Newren via GitGitGadget wrote:\n> > > >\n> > > > > From: Elijah Newren <newren@gmail.com>\n> > > > >\n> > > [...]\n> > > > > diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> > > > > index fd7a867de60..041a4ac2785 100644\n> > > > > --- a/Documentation/git-merge-tree.txt\n> > > > > +++ b/Documentation/git-merge-tree.txt\n> > > > > @@ -58,6 +58,7 @@ simply one line:\n> > > > >  Whereas for a conflicted merge, the output is by default of the form:\n> > > > >\n> > > > >       <OID of toplevel tree>\n> > > > > +     <Conflicted file list>\n> > > > >       <Informational messages>\n> > > >\n> > > > To distinguish between the list of conflicted files and the informational\n> > > > messages, I think it would be good to insert an empty line, as a\n> > > > separator, like.\n> > >\n> > > Yes, I agree; that's why I did so.  :-)\n> >\n> > My concern was that I did not see this empty line reflected in the quoted\n> > diff. I would have expected an empty line between the `<Conflicted [...]>`\n> > and the `<Informational [...]>` line.\n>\n> As stated later in the same email, the newline is only printed if the\n> <Informational messages> section is printed.  As such, it's part of\n> the <Informational messages> section and listing it between the\n> sections could be misleading.\n\nThank you for the clarification. I still think that it might cause\nconfusion in the documentation not to see the explicit newline, quite\npossible when _I_ re-read this in six months from now. But then, if `-z`\nis in effect, it won't be an explicit newline, it will be a NUL instead.\n\nIn short: I am fine with leaving this as-is, it might actually reduce even\n_my_ confusion to the minimum possible.\n\nThanks,\nDscho\n"},{"id":"448967","messageId":"nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-21T10:46:10Z","receivedAt":"2022-02-21T11:12:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Fri, 4 Feb 2022, Elijah Newren wrote:\n\n> On Fri, Feb 4, 2022 at 3:10 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Sat, 29 Jan 2022, Elijah Newren wrote:\n> >\n> > > On Sat, Jan 29, 2022 at 12:23 AM Johannes Sixt <j6t@kdbg.org> wrote:\n> > > >\n> > > > Just a heckling from the peanut gallery...\n> > > >\n> > > > Am 29.01.22 um 07:08 schrieb Elijah Newren:\n> > > > > On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n> > > > > <Johannes.Schindelin@gmx.de> wrote:\n> > > > >> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n> > > > >> missing from the second conflict, in the output we would see stages 1, 2,\n> > > > >> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n> > > > >> different conflicts.\n> > > > >\n> > > > > I don't understand why you're fixating on the stage here.  Why would\n> > > > > you want to group all the stage 2s together, count them up, and then\n> > > > > determine there are N conflicting files because there are N stage 2's?\n> > > >\n> > > > Looks like you are misunderstanding Dscho's point: When you have two\n> > > > conflicts, the first with stages 1 and 2, the second with stages 2 and\n> > > > 3, then the 2s occur lumped together when the 4 lines are printed in a\n> > > > row, and that is the cue to the parser where the new conflict begins.\n> > > > Dscho did not mean that all N 2s of should be listed together.\n> > >\n> > > Ah, so...I didn't understand his misunderstanding?  Using stages as a\n> > > cue to the parser where the new conflict begins is broken; you should\n> > > instead check for when the filename listed on a line does not match\n> > > the filename on the previous line.\n> >\n> > But that would break down in case of rename/rename conflicts, right?\n> >\n> > > In particular, if one conflict has stages 1 and 2, and the next conflict\n> > > has only stage 3, then looking at stages only might cause you to\n> > > accidentally lump unrelated conflicts together.\n> >\n> > Precisely. That's why I would love to have a way to deviate from the\n> > output of `ls-files -u`'s format, and have a reliable way to indicate\n> > stages that belong to the same merge conflict.\n>\n> Ah, attempting to somehow identify and present logical separate\n> conflicts?  That could be awesome, but I'm not sure it's technically\n> possible.  It certainly isn't with today's merge-ort.\n>\n> Let me ask some questions first...\n>\n> If I understand you correctly then in the event of a rename/rename,\n> i.e. foo->bar & foo->baz, then you want foo's, bar's, & baz's stages\n> all listed together.  Right?  And in some way that you can identify\n> them as related?\n\nYes, that was kind of my idea ;-)\n\n> If we do so, how do we mark the beginning and the end of what you call\n> \"the same merge conflict\"?  If you say it's always 3 stages (with the\n> possibility of all-zero modes/oids), then what about the rename/rename\n> case above modified so that the side that did foo->baz also added a\n> different 'bar'?  That'd be 4 non-zero modes/oids, all of them\n> relevant.  Or what if the side that did foo->bar also renamed\n> something else to 'baz', giving us even more non-zero stages for these\n> three paths?  Perhaps you consider these different conflicts and want\n> them listed separately -- if so, where does one conflict begin and\n> another start and which stages are parts of which conflict?\n\nThank you for calling me out on an only half-finished thought.\n\nTo be quite honest, I previously did not have much time to think of the\nnon-trivial nature of representing merge conflicts in a web UI when\nperforming merges with rename detection, in particular when performing\n_recursive_ merges (where, as you point out so correctly, an arbitrary\nnumber of rename conflicts can happen _for the same file_).\n\nOf course, this gets even more complicated when we're also thinking about\ntype changes (file -> symlink, file -> directory, and vice versa).\n\n> If you are attempting to somehow present the stuff that \"belongs to\n> the same merge conflict\" are you also trying to identify what kind of\n> merge conflict it is?  If so, do you want each type of merge conflict\n> listed?  For example, let's switch from the example above of logically\n> disjoint paths coming together to result in more than 3 stages, and\n> instead pick an example with a single logical path with less than\n> three stages.  And now let's say that path has multiple conflicts\n> associated with it; let's use an example with 3: rename/delete +\n> modify/delete + directory/file (one side renames foo->bar while\n> modifying the contents, the other side deletes foo and adds the\n> directory 'bar/').  In this case, there is a target file 'bar' that\n> has two non-zero modes/oids in the ls-files-u output.  If all three\n> types of conflicts need to be listed, does each need to be listed with\n> the two non-zero modes/oids (and perhaps one zero mode/oid), resulting\n> in six listings for 'bar'?  Or would the duplication be confusing\n> enough that we instead decide to list some merge conflicts with no\n> stages associated with them?\n>\n> Thinking about both sets of questions in the last two paragraphs from\n> a higher level -- should we focus on and group the higher order stages\n> by the individual conflicts that happen, or should we group them by\n> the paths that they happen to (which is what `ls-files -u` happens to\n> do), or should we not bother grouping them and instead duplicate the\n> higher order stages for each logical conflict it is part of?\n>\n> As an alternative to duplicating higher order stages, do we sometimes\n> decide to \"lump\" separate conflicts together and treat them as one\n> conflict?  If so, what are the rules on how we decide to lump\n> conflicts and when not to?  Is there a bright line boundary?  And can\n> it be done without sucking in arbitrarily more stages for a single\n> conflict?\n>\n>\n> Some testcases that might be useful while considering the above\n> questions: take a look at the \"rad\", \"rrdd\", and \"mod6\" tests of\n> t6422.  How many \"same merge conflicts\" are there for each of those,\n> and what's the boundary between them?  And can you give the answer in\n> the form of rules that generically handle all cases, rather than just\n> answering these three specific cases?\n>\n>\n> I've thought about this problem long and hard before (in part because\n> of some conversations I had with Edward Thompson about libgit2 and\n\nNot a big deal for _me_, but I seem to remember that Ed cared a lot about\nhaving no p in their surname ;-)\n\n> merging at Git Merge 2020).  It wasn't at all clear to me that libgit2\n> had considered anything beyond simple rename cases.  The only rules I\n> ever figured out that made sense to me was \"group the stages by target\n> filename rather than by logical conflict\" (so we get `ls -files -u`\n> populated) and print a meant-for-human message for each logical\n> conflict (found in the <Informational Messages> section for\n> merge-tree), and make NO attempt to connect stages by conflict type.\n>\n> I'm sure that's not what you wanted to hear, and maybe doesn't even\n> play nicely with your design.  But short of ignoring the edge and\n> corner cases, I don't see how to solve that problem.  If you do just\n> want to ignore edge and corner cases, then just ignore the\n> rename/rename case you brought up in the first place and just use\n> `ls-files -u`-type output as-is within your design.  If you don't want\n> to ignore edge cases and want something that works with a specific\n> design that somehow groups conflicted file stages by conflict type,\n> then we're going to have to dig into all these questions above and do\n> some big replumbing within merge-ort.\n\nThere is sometimes a big difference between what I want to hear and what I\nneed to hear. Thank you for providing so many details that I needed to\nhear.\n\nSo let's take a step back and look at my goal here, as in: the\nover-arching goal: to use merge-ort on the server side.\n\nFrom what you said above, it becomes very clear to me that there is very\nlittle chance to resolve such conflicts on the server side.\n\nFor example, if a topic branch renames a file differently than the main\nbranch, there is a really good chance that the user tasked with merging\nthe topic branch will have to do a whole lot more than just click a few\nbuttons to perform that task. There might very well be the need to edit\nfiles that do not contain merge conflict markers (I like to call those\ncases \"non-semantic merge conflicts\"), and almost certainly local testing\nwill be necessary.\n\nSo I guess the best we can do in those complicated cases is to give a\ncomprehensive overview of the problems in the web UI, with the note that\nthis merge conflict has to be resolved on the local side.\n\nWhich brings me to the next concern: since `merge-tree` is a low-level\ntool meant to be called by programs rather than humans, we need to make\nsure that those messages remain machine-parseable, even if they contain\nfile names.\n\nConcretely: while I am not currently aware of any web UI that allows to\nresolve simple rename/rename conflicts, it is easily conceivable how to\nimplement such a thing. When that happens, we will need to be able to\nteach the server-side code to discern between the cases that can be\nhandled in the web UI (trivial merge conflicts, trivial rename/rename\nconflicts) as compared to scenarios where the conflicts are just too\ncomplex.\n\nHere's an excerpt from t4301:\n\n-- snip --\nAuto-merging greeting\nCONFLICT (content): Merge conflict in greeting\nAuto-merging numbers\nCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\nCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n-- snap --\n\nThis is the complete set of messages provided in the `test conflict\nnotices and such` test case.\n\nI immediately notice that every line contains at least one file name.\nLooking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\ndoes not seem as if the file names are quoted:\n\n\t\tpath_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n\n(where `path` is used verbatim in a call to `merge_3way()` before that,\ni.e. it must not have been quoted)\n\nI would like to register a wish to ensure that file names with special\ncharacters (such as most notably line-feed characters) are quoted in these\nmessages, so that a simple server-side parser can handle messages starting\nwith `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n\"throw the hands up in the air\" if any other message prefix is seen.\n\nDo you think we can switch to `sq_quote_buf_pretty()` for these messages?\nFor the `Auto-merging` one, it would be trivial, but I fear that we will\nhave to work a bit on the `path_msg()` function\n(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\naccepts a variable list of arguments without any clue whether the\narguments refer to paths or not. (And I would be loathe to switch _all_\ncallers to do the quoting themselves.)\n\nI see 28 calls to that function, and at least a couple that pass not only\na path but also an OID (e.g.\nhttps://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n\nWe could of course be sloppy and pass even OIDs through\n`sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\nspecial characters in them, but it gets more complicated e.g. in\nhttps://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\npass an `strbuf` that contains a somewhat free-form commit message.\n\nI guess we could still pass those through `sq_quote_buf_pretty()`, even if\nthey are not paths, to ensure that there are no special characters in the\nmachine-parseable lines.\n\nWhat do you think?\n\nCiao,\nDscho\n"},{"id":"448977","messageId":"220221.8635kccn0u.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-21T14:27:56Z","receivedAt":"2022-02-21T14:28:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Feb 21 2022, Johannes Schindelin wrote:\n\n> Hi Elijah,\n>\n> On Fri, 4 Feb 2022, Elijah Newren wrote:\n>\n>> On Fri, Feb 4, 2022 at 3:10 PM Johannes Schindelin\n>> <Johannes.Schindelin@gmx.de> wrote:\n>> >\n>> > On Sat, 29 Jan 2022, Elijah Newren wrote:\n>> >\n>> > > On Sat, Jan 29, 2022 at 12:23 AM Johannes Sixt <j6t@kdbg.org> wrote:\n>> > > >\n>> > > > Just a heckling from the peanut gallery...\n>> > > >\n>> > > > Am 29.01.22 um 07:08 schrieb Elijah Newren:\n>> > > > > On Fri, Jan 28, 2022 at 8:55 AM Johannes Schindelin\n>> > > > > <Johannes.Schindelin@gmx.de> wrote:\n>> > > > >> Meaning: Even if stage 3 is missing from the first conflict and stage 1 is\n>> > > > >> missing from the second conflict, in the output we would see stages 1, 2,\n>> > > > >> 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two\n>> > > > >> different conflicts.\n>> > > > >\n>> > > > > I don't understand why you're fixating on the stage here.  Why would\n>> > > > > you want to group all the stage 2s together, count them up, and then\n>> > > > > determine there are N conflicting files because there are N stage 2's?\n>> > > >\n>> > > > Looks like you are misunderstanding Dscho's point: When you have two\n>> > > > conflicts, the first with stages 1 and 2, the second with stages 2 and\n>> > > > 3, then the 2s occur lumped together when the 4 lines are printed in a\n>> > > > row, and that is the cue to the parser where the new conflict begins.\n>> > > > Dscho did not mean that all N 2s of should be listed together.\n>> > >\n>> > > Ah, so...I didn't understand his misunderstanding?  Using stages as a\n>> > > cue to the parser where the new conflict begins is broken; you should\n>> > > instead check for when the filename listed on a line does not match\n>> > > the filename on the previous line.\n>> >\n>> > But that would break down in case of rename/rename conflicts, right?\n>> >\n>> > > In particular, if one conflict has stages 1 and 2, and the next conflict\n>> > > has only stage 3, then looking at stages only might cause you to\n>> > > accidentally lump unrelated conflicts together.\n>> >\n>> > Precisely. That's why I would love to have a way to deviate from the\n>> > output of `ls-files -u`'s format, and have a reliable way to indicate\n>> > stages that belong to the same merge conflict.\n>>\n>> Ah, attempting to somehow identify and present logical separate\n>> conflicts?  That could be awesome, but I'm not sure it's technically\n>> possible.  It certainly isn't with today's merge-ort.\n>>\n>> Let me ask some questions first...\n>>\n>> If I understand you correctly then in the event of a rename/rename,\n>> i.e. foo->bar & foo->baz, then you want foo's, bar's, & baz's stages\n>> all listed together.  Right?  And in some way that you can identify\n>> them as related?\n>\n> Yes, that was kind of my idea ;-)\n>\n>> If we do so, how do we mark the beginning and the end of what you call\n>> \"the same merge conflict\"?  If you say it's always 3 stages (with the\n>> possibility of all-zero modes/oids), then what about the rename/rename\n>> case above modified so that the side that did foo->baz also added a\n>> different 'bar'?  That'd be 4 non-zero modes/oids, all of them\n>> relevant.  Or what if the side that did foo->bar also renamed\n>> something else to 'baz', giving us even more non-zero stages for these\n>> three paths?  Perhaps you consider these different conflicts and want\n>> them listed separately -- if so, where does one conflict begin and\n>> another start and which stages are parts of which conflict?\n>\n> Thank you for calling me out on an only half-finished thought.\n>\n> To be quite honest, I previously did not have much time to think of the\n> non-trivial nature of representing merge conflicts in a web UI when\n> performing merges with rename detection, in particular when performing\n> _recursive_ merges (where, as you point out so correctly, an arbitrary\n> number of rename conflicts can happen _for the same file_).\n>\n> Of course, this gets even more complicated when we're also thinking about\n> type changes (file -> symlink, file -> directory, and vice versa).\n>\n>> If you are attempting to somehow present the stuff that \"belongs to\n>> the same merge conflict\" are you also trying to identify what kind of\n>> merge conflict it is?  If so, do you want each type of merge conflict\n>> listed?  For example, let's switch from the example above of logically\n>> disjoint paths coming together to result in more than 3 stages, and\n>> instead pick an example with a single logical path with less than\n>> three stages.  And now let's say that path has multiple conflicts\n>> associated with it; let's use an example with 3: rename/delete +\n>> modify/delete + directory/file (one side renames foo->bar while\n>> modifying the contents, the other side deletes foo and adds the\n>> directory 'bar/').  In this case, there is a target file 'bar' that\n>> has two non-zero modes/oids in the ls-files-u output.  If all three\n>> types of conflicts need to be listed, does each need to be listed with\n>> the two non-zero modes/oids (and perhaps one zero mode/oid), resulting\n>> in six listings for 'bar'?  Or would the duplication be confusing\n>> enough that we instead decide to list some merge conflicts with no\n>> stages associated with them?\n>>\n>> Thinking about both sets of questions in the last two paragraphs from\n>> a higher level -- should we focus on and group the higher order stages\n>> by the individual conflicts that happen, or should we group them by\n>> the paths that they happen to (which is what `ls-files -u` happens to\n>> do), or should we not bother grouping them and instead duplicate the\n>> higher order stages for each logical conflict it is part of?\n>>\n>> As an alternative to duplicating higher order stages, do we sometimes\n>> decide to \"lump\" separate conflicts together and treat them as one\n>> conflict?  If so, what are the rules on how we decide to lump\n>> conflicts and when not to?  Is there a bright line boundary?  And can\n>> it be done without sucking in arbitrarily more stages for a single\n>> conflict?\n>>\n>>\n>> Some testcases that might be useful while considering the above\n>> questions: take a look at the \"rad\", \"rrdd\", and \"mod6\" tests of\n>> t6422.  How many \"same merge conflicts\" are there for each of those,\n>> and what's the boundary between them?  And can you give the answer in\n>> the form of rules that generically handle all cases, rather than just\n>> answering these three specific cases?\n>>\n>>\n>> I've thought about this problem long and hard before (in part because\n>> of some conversations I had with Edward Thompson about libgit2 and\n>\n> Not a big deal for _me_, but I seem to remember that Ed cared a lot about\n> having no p in their surname ;-)\n>\n>> merging at Git Merge 2020).  It wasn't at all clear to me that libgit2\n>> had considered anything beyond simple rename cases.  The only rules I\n>> ever figured out that made sense to me was \"group the stages by target\n>> filename rather than by logical conflict\" (so we get `ls -files -u`\n>> populated) and print a meant-for-human message for each logical\n>> conflict (found in the <Informational Messages> section for\n>> merge-tree), and make NO attempt to connect stages by conflict type.\n>>\n>> I'm sure that's not what you wanted to hear, and maybe doesn't even\n>> play nicely with your design.  But short of ignoring the edge and\n>> corner cases, I don't see how to solve that problem.  If you do just\n>> want to ignore edge and corner cases, then just ignore the\n>> rename/rename case you brought up in the first place and just use\n>> `ls-files -u`-type output as-is within your design.  If you don't want\n>> to ignore edge cases and want something that works with a specific\n>> design that somehow groups conflicted file stages by conflict type,\n>> then we're going to have to dig into all these questions above and do\n>> some big replumbing within merge-ort.\n>\n> There is sometimes a big difference between what I want to hear and what I\n> need to hear. Thank you for providing so many details that I needed to\n> hear.\n>\n> So let's take a step back and look at my goal here, as in: the\n> over-arching goal: to use merge-ort on the server side.\n>\n> From what you said above, it becomes very clear to me that there is very\n> little chance to resolve such conflicts on the server side.\n>\n> For example, if a topic branch renames a file differently than the main\n> branch, there is a really good chance that the user tasked with merging\n> the topic branch will have to do a whole lot more than just click a few\n> buttons to perform that task. There might very well be the need to edit\n> files that do not contain merge conflict markers (I like to call those\n> cases \"non-semantic merge conflicts\"), and almost certainly local testing\n> will be necessary.\n>\n> So I guess the best we can do in those complicated cases is to give a\n> comprehensive overview of the problems in the web UI, with the note that\n> this merge conflict has to be resolved on the local side.\n>\n> Which brings me to the next concern: since `merge-tree` is a low-level\n> tool meant to be called by programs rather than humans, we need to make\n> sure that those messages remain machine-parseable, even if they contain\n> file names.\n>\n> Concretely: while I am not currently aware of any web UI that allows to\n> resolve simple rename/rename conflicts, it is easily conceivable how to\n> implement such a thing. When that happens, we will need to be able to\n> teach the server-side code to discern between the cases that can be\n> handled in the web UI (trivial merge conflicts, trivial rename/rename\n> conflicts) as compared to scenarios where the conflicts are just too\n> complex.\n>\n> Here's an excerpt from t4301:\n>\n> -- snip --\n> Auto-merging greeting\n> CONFLICT (content): Merge conflict in greeting\n> Auto-merging numbers\n> CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n> CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n> -- snap --\n>\n> This is the complete set of messages provided in the `test conflict\n> notices and such` test case.\n>\n> I immediately notice that every line contains at least one file name.\n> Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\n> does not seem as if the file names are quoted:\n>\n> \t\tpath_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n>\n> (where `path` is used verbatim in a call to `merge_3way()` before that,\n> i.e. it must not have been quoted)\n>\n> I would like to register a wish to ensure that file names with special\n> characters (such as most notably line-feed characters) are quoted in these\n> messages, so that a simple server-side parser can handle messages starting\n> with `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n> \"throw the hands up in the air\" if any other message prefix is seen.\n>\n> Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n> For the `Auto-merging` one, it would be trivial, but I fear that we will\n> have to work a bit on the `path_msg()` function\n> (https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\n> accepts a variable list of arguments without any clue whether the\n> arguments refer to paths or not. (And I would be loathe to switch _all_\n> callers to do the quoting themselves.)\n>\n> I see 28 calls to that function, and at least a couple that pass not only\n> a path but also an OID (e.g.\n> https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n>\n> We could of course be sloppy and pass even OIDs through\n> `sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\n> special characters in them, but it gets more complicated e.g. in\n> https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\n> pass an `strbuf` that contains a somewhat free-form commit message.\n>\n> I guess we could still pass those through `sq_quote_buf_pretty()`, even if\n> they are not paths, to ensure that there are no special characters in the\n> machine-parseable lines.\n>\n> What do you think?\n>\n> Ciao,\n> Dscho\n\n"},{"id":"448979","messageId":"220221.86y224b80b.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet","subject":"machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-21T14:28:15Z","receivedAt":"2022-02-21T14:37:45Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Feb 21 2022, Johannes Schindelin wrote:\n\n[I sent out an empty reply to this earlier by mistake, sorry about that]\n\n> [...]\n> Which brings me to the next concern: since `merge-tree` is a low-level\n> tool meant to be called by programs rather than humans, we need to make\n> sure that those messages remain machine-parseable, even if they contain\n> file names.\n>\n> Concretely: while I am not currently aware of any web UI that allows to\n> resolve simple rename/rename conflicts, it is easily conceivable how to\n> implement such a thing. When that happens, we will need to be able to\n> teach the server-side code to discern between the cases that can be\n> handled in the web UI (trivial merge conflicts, trivial rename/rename\n> conflicts) as compared to scenarios where the conflicts are just too\n> complex.\n>\n> Here's an excerpt from t4301:\n>\n> -- snip --\n> Auto-merging greeting\n> CONFLICT (content): Merge conflict in greeting\n> Auto-merging numbers\n> CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n> CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n> -- snap --\n>\n> This is the complete set of messages provided in the `test conflict\n> notices and such` test case.\n>\n> I immediately notice that every line contains at least one file name.\n> Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\n> does not seem as if the file names are quoted:\n>\n> \t\tpath_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n>\n> (where `path` is used verbatim in a call to `merge_3way()` before that,\n> i.e. it must not have been quoted)\n>\n> I would like to register a wish to ensure that file names with special\n> characters (such as most notably line-feed characters) are quoted in these\n> messages, so that a simple server-side parser can handle messages starting\n> with `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n> \"throw the hands up in the air\" if any other message prefix is seen.\n>\n> Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n> For the `Auto-merging` one, it would be trivial, but I fear that we will\n> have to work a bit on the `path_msg()` function\n> (https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\n> accepts a variable list of arguments without any clue whether the\n> arguments refer to paths or not. (And I would be loathe to switch _all_\n> callers to do the quoting themselves.)\n>\n> I see 28 calls to that function, and at least a couple that pass not only\n> a path but also an OID (e.g.\n> https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n>\n> We could of course be sloppy and pass even OIDs through\n> `sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\n> special characters in them, but it gets more complicated e.g. in\n> https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\n> pass an `strbuf` that contains a somewhat free-form commit message.\n>\n> I guess we could still pass those through `sq_quote_buf_pretty()`, even if\n> they are not paths, to ensure that there are no special characters in the\n> machine-parseable lines.\n>\n> What do you think?\n\nThat sounds like a rather nasty hack, this is too, but demonstrates that\nwe can pretty easily extract this in a machine-readable format with just\na few lines now):\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 8a5f201d190..a906881f9b3 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -633,7 +633,7 @@ static void path_msg(struct merge_options *opt,\n \t\t     int omittable_hint, /* skippable under --remerge-diff */\n \t\t     const char *fmt, ...)\n {\n-\tva_list ap;\n+\tva_list ap, cp;\n \tstruct strbuf *sb, *dest;\n \tstruct strbuf tmp = STRBUF_INIT;\n \n@@ -650,7 +650,9 @@ static void path_msg(struct merge_options *opt,\n \n \tdest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);\n \n+\tva_copy(cp, ap);\n \tva_start(ap, fmt);\n+\n \tif (opt->priv->call_depth) {\n \t\tstrbuf_addchars(dest, ' ', 2);\n \t\tstrbuf_addstr(dest, \"From inner merge:\");\n@@ -659,6 +661,15 @@ static void path_msg(struct merge_options *opt,\n \tstrbuf_vaddf(dest, fmt, ap);\n \tva_end(ap);\n \n+\tva_start(cp, fmt);\n+\ttrace2_region_enter_printf(\"merge\", \"conflict/path\", opt->repo, \"%s\", path);\n+\ttrace2_region_leave(\"merge\", \"conflict/path\", opt->repo);\n+\ttrace2_region_enter_printf(\"merge\", \"conflict/fmt\", opt->repo, \"%s\", fmt);\n+\ttrace2_region_leave(\"merge\", \"conflict/fmt\", opt->repo);\n+\ttrace2_region_enter_printf_va(\"merge\", \"conflict/msg\", opt->repo, fmt, cp);\n+\ttrace2_region_leave(\"merge\", \"conflict/msg\", opt->repo);\n+\tva_end(cp);\n+\n \tif (opt->record_conflict_msgs_as_headers) {\n \t\tint i_sb = 0, i_tmp = 0;\n \nYou can run that with one of the tests added in this series to get the\noutput as JSON, e.g.:\n\n     GIT_TRACE2_EVENT=/dev/stderr GIT_TRACE2_EVENT_NESTING=10 ~/g/git/git merge-tree --write-tree --no-messages --name-only --messages side1 side2 2>&1|jq -r .| grep '\"msg\"'\n      \"msg\": \"whatever~side1\"\n      \"msg\": \"CONFLICT (file/directory): directory in the way of %s from %s; moving it to %s instead.\"\n      \"msg\": \"CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\"\n      \"msg\": \"whatever~side1\"\n      \"msg\": \"CONFLICT (modify/delete): %s deleted in %s and modified in %s.  Version %s of %s left in tree.\"\n      \"msg\": \"CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\"\n      \"msg\": \"numbers\"\n      \"msg\": \"Auto-merging %s\"\n      \"msg\": \"Auto-merging numbers\"\n      \"msg\": \"greeting\"\n      \"msg\": \"Auto-merging %s\"\n      \"msg\": \"Auto-merging greeting\"\n      \"msg\": \"greeting\"\n      \"msg\": \"CONFLICT (%s): Merge conflict in %s\"\n      \"msg\": \"CONFLICT (content): Merge conflict in greeting\"\n\nA \"proper\" fix for this doesn't sound too hard, we'd just instrument the\npath_msg() function to pass along some \"message category\", see\ne.g. unpack_plumbing_errors in unpack-trees.c for one example of such a\nthing, or the \"enum fsck_msg_id\".\n\nThen we'd just allow you to emit any of the sprintf() format itself, or\nthe expanded version, the path, or an id like \"CONFLICT:file/directory\"\nor \"auto-merging\" etc.\n\nI think that would be particularly useful in conjuction with the\n--format changes I was proposing for this (and hacked up a WIP patch\nfor). You could just have a similar --format-messages or whatever.\n\nThen you could pick \\0\\0 as a delimiter for your \"main\" --format, and\n\"\\0\" as the delimiter for your --format-messages, and thus be able to\nparse N-nested \\0-delimited content.\n\n\n\n\n\n"},{"id":"449039","messageId":"xmqqee3wt5g3.fsf@gitster.g","threadId":"57288","inReplyTo":"CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-21T18:55:40Z","receivedAt":"2022-02-21T18:56: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> Adding such an ability to merge-tree would be trivial -- it basically\n> involves just two things: (1) accepting one extra argument, and (2)\n> calling merge_incore_nonrecursive() instead of\n> merge_incore_recursive().\n>\n> However, I think forking a subprocess for every merge of a series of\n> commits is a completely unreasonable overhead, so even if we provide\n> such an option to merge-tree, I still want a separate plumbing-ish\n> tool that does non-worktree/non-index replaying of commits which is\n> not written as a driver of merge-tree.  That other tool should just\n> call merge_incore_nonrecursive() directly.  And such a tool, since it\n> should handle an arbitrary number of commits, should certainly be able\n> to handle just one commit.  From that angle, it feels like adding\n> another mode to merge-tree would just be a partial duplication of the\n> other tool.\n\nThe above does not make much sense to me.\n\nI am hearing that \"multi-step cherry-picks and reverts need to be\nfast and we need something like sequencer that is all written in C,\nand single-step cherry-pick is merely a special case that does not\ndeserve a plumbing\".\n\nBut that argument leads to \"and the same something-like-sequencer\nthat is all written in C would need '--rebase-merges' that can pick\nmulti-step merge sequences, and single-step merge does not deserve a\nplumbing\", which is an argument against this topic that is utterly\nabsurd.\n\nSo why isn't your objection not equally absurd against having a\nsingle step cherry-pick or revert primitive as a plumbing?\n\n"},{"id":"449081","messageId":"CABPp-BGzWOqgsiRx0jAitbriCCyP8GaVHcCmYV-+CAJZXf1f-w@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202211011510.26495@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-22T01:54:33Z","receivedAt":"2022-02-22T01:54:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 21, 2022 at 1:13 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 3 Feb 2022, Elijah Newren wrote:\n>\n> > On Thu, Feb 3, 2022 at 8:24 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> > >\n> > > On Thu, Feb 03 2022, Elijah Newren wrote:\n> > >\n> > > > Man, what a can of worms this all is.  Maybe I really should just drop\n> > > > patches 5, 6, and 8 for now...\n> > >\n> > > Yeah, I really think it's worth it to just sprinkle a tiny bit of\n> > > if/else (or a macro) here and print to stderr inline or not. We can make\n> > > some use of some usage.c when there's good reason to do so, but this bit\n> > > just seems like a needless digression.\n> > >\n> > > I hope all of this has helped somewhat ...\n> >\n> > Absolutely; thanks for reviewing!  These parts may just end up in me\n> > dropping some patches for now (since they're not actually being used\n> > anyway), but I think it's all good feedback.\n>\n> So we dropped some useful patches future-proofing `merge-tree` for the\n> sake of appeasing a refactoring with no immediately obvious benefit? I\n> really don't like that direction.\n\nEven before any of Ævar's comments, I had already noted on my cover\nletter[1] that \"to be honest, patches 5, 6, & 8 may be less relevant\nsince we're now including these messages on stdout anyway\" -- so I was\nalready wondering if I should defer them to some future series.  Then\nwhen Ævar reviewed the series, he noted (1) that I lacked tests of\nthese changes (which is true, because nothing uses them, and I can't\neasily add a test as I have no current usecase in mind), and (2) these\npatches would print a \"warning: \" prefix when printing to stdout but\nnot print such a prefix otherwise, which felt inconsistent.  Those\nseemed to reinforce the comment I had already made that these changes\nwere unused in my series and maybe should be separated.  I still like\nthe general idea behind the future proofing you did here, so maybe I\nwas just being lazy, but between those factors I decided that punting\nuntil later made sense.\n\n[1] https://lore.kernel.org/git/pull.1122.v3.git.1643787281.gitgitgadget@gmail.com/\n"},{"id":"449082","messageId":"CABPp-BF0LtkDPxBY7ECbS4110-38sa=YXBkUkjw6mNFH4PEebQ@mail.gmail.com","threadId":"57288","inReplyTo":"220220.86k0dpd8c8.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-22T02:08:50Z","receivedAt":"2022-02-22T02:09:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sun, Feb 20, 2022 at 4:35 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Sun, Feb 20 2022, Elijah Newren via GitGitGadget wrote:\n>\n> > == Updates Log ==\n> >\n> > Many thanks to the many reviewers who provided good feedback on the most\n> > recent round -- Junio, Ævar, Josh, Emily, and perhaps some others I've\n> > forgotten from review club.\n> >\n> > Updates since v4:\n> >\n> >  * Fixed double \"is\" in documentation.\n> >  * Fixed a few small items with testcases\n> >\n> > Updates since v3 (or v5, if you include the rounds at\n> > https://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/):\n> >\n> >  * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n> >    being used and opened a can of worms[1]\n> >  * [Patch 3] Restructured argument checking, including using an enum\n> >  * [Patch 4] Restored the extended paragraph about the deprecated form of\n> >    git-merge-tree, mentioned write-tree in plumbing commands, and a few\n> >    other small fixups to the documentation\n> >  * [Patch 4] Also provide an example of a clean merge rather than just a\n> >    conflicted one\n> >  * [Patch 6] Fix the incompatible arguments check and add some tests for it\n> >  * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n> >    easier to read (less repeated sed)\n> >  * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n> >    option for now\n> >  * [Patch 10] When -z passed, the tree in the first section should have a\n> >    trailing NUL rather than trailing newline [1]\n> >    https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n> >\n> > Stuff NOT included that reviewers brought up in earlier rounds:\n> >\n> >  * Very generic (mode, oid, stage, filename) printing formatting[2]\n> >  * Always printing 3 stages for each filename with conflicts[3]\n> >  * Attempting to group conflict stages by logical conflict rather than by\n> >    affected target filepath[4]\n> >  * Providing similar functionality for doing cherry-picks/rebases/reverts,\n> >    i.e. a scheme for three-way merges with a specified merge-base[5]. That's\n> >    being deferred to a future series. [2]\n> >    https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n> >    [3]\n> >    https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n> >    [4]\n> >    https://lore.kernel.org/git/CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com/\n> >    [5]\n> >    https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n>\n> I've looked through this, I think it all looks good overall & that the\n> things that needed to be addressed (as opposed to my --format rambling)\n> have been.\n>\n> I think all the code should be ready for \"next\".\n>\n> I suggested (I think around getopts discussion) in an earlier that the\n> code would have been easier with a new built-in, but if we're\n> deprecating the existing \"mode\" I think using the name is probably\n> better in the end.\n>\n> I find the resulting documentation to be really hard to grok though\n> because we're effectively describing two different commands. The current\n> docs are small: https://git-scm.com/docs/git-merge-tree\n>\n> I built the tip of this series and read the manpage, and found myself\n> needing to carefully squint to see what referred to what mode in the\n> docs.\n>\n> E.g. by the time it's discussing \"-z\" and other options the reader needs\n> to be astute really aware of the context, and infer from the lack of\n> \"<options>\" on the \"--trivial-merge\" that these options refer to the\n> \"--write-tree\" only.\n>\n> The same goes for the rest of \"--trivial-merge\". I.e. I found myself\n> needing to read the whole docs word-by-word (no skimming!) to see if\n> OUTPUT etc. was going to describe its output, or just the \"new\" mode.\n>\n> It is my NSHO that man pages should be structured for the impatient\n> reader :)\n>\n> Then when you say \"git-merge-tree was written to be[...]\" I thought \"ah\n> ha! surely this will discuss the since-2005 implemented mode\", but \"was\n> written to be\" is referring to code new in this series.\n>\n> The below patch-on-top addresses all those concerns. Basically I just\n> added a line to the top of the DESCRIPTION saying that you should read a\n> \"DEPRECATED DESCRIPTION\" section at the end for \"--trivial-merge\", and\n> that all of the rest is talking about the \"--write-tree\" mode.\n>\n> I then edited various prose to do away with the now-unnecessary \"the\n> first form\" etc.\n>\n> This diff is better against \"master\" in that you'll see that the current\n> merge-tree DESCRIPTION section isn't touched at all (it's now just under\n> a new heading), but this diff is against the tip of your series.\n>\n> There's various other small fixes while I was it it here, e.g. all your\n> cross-section links were using some pseudo-not-quite-ASCIIDOC syntax\n> that doesn't work. Now it uses the right syntax. Ditto link-ifying\n> references to \"mktag\" etc.\n>\n> I don't know if you'd consider this for a v6, or if I should just submit\n> this on top myself, but in any case here it is. I'll leave it to you how\n> you'd like to proceed with it:\n\nOverall the diff below looks good; thanks!  I'll include it in a v6.\nTwo exceptions, listed below:\n\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index 306149fa0e2..723b1995426 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -9,17 +9,24 @@ git-merge-tree - Perform merge without touching index or working tree\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n> -'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n> +'git merge-tree' --write-tree [<options>] <branch1> <branch2>\n> +'git merge-tree' --trivial-merge <base-tree> <branch1> <branch2>\n\nI dislike all three changes to the synopsis; I'll discard these.\n\n> +[[NEWMERGE]]\n>  DESCRIPTION\n>  -----------\n>\n> -Performs a merge, but does not make any new commits and does not read\n> -from or write to either the working tree or index.\n> +This command has a modern `--write-tree` mode and a deprecated\n> +`--trivial-merge` mode. The rest of this documentation describes\n> +modern `--write-tree` mode unless otherwise specified. see\n> +<<DEPMERGE,DEPRECATED DESCRIPTION>> below for a summary of the\n> +`--trivial-merge` mode.\n>\n> -The first form will merge the two branches, doing a real merge.  A real\n> -merge is distinguished from a trivial merge in that it includes:\n> +Performs a \"real\" merge, but does not make any new commits and does\n> +not read from or write to either the working tree or index.\n> +\n> +The performed merge will use the same feature as the \"real\"\n> +linkgit:git-merge[1], including:\n>\n>    * three way content merges of individual files\n>    * rename detection\n> @@ -28,24 +35,8 @@ merge is distinguished from a trivial merge in that it includes:\n>      merge base, creating a virtual merge base by merging the merge bases)\n>    * etc.\n>\n> -After the merge completes, the first form will create a new toplevel\n> -tree object.  See `OUTPUT` below for details.\n> -\n> -The second form is deprecated; it is kept for backward compatibility\n> -reasons but may be deleted in the future.  Other than the optional\n> -`--trivial-merge`, it accepts no options.  It can only do a trivial\n> -merge.  It reads three tree-ish, and outputs trivial merge results and\n> -conflicting stages to the standard output in a semi-diff format.\n> -Since this was designed for higher level scripts to consume and merge\n> -the results back into the index, it omits entries that match\n> -<branch1>.  The result of this second form is similar to what\n> -three-way 'git read-tree -m' does, but instead of storing the results\n> -in the index, the command outputs the entries to the standard output.\n> -This form not only has limited applicability, the output format is\n> -also difficult to work with, and it will generally be less performant\n> -than the first form even on successful merges (especially if working\n> -in large repositories).  The remainder of this manual will only\n> -discuss the first form.\n> +After the merge completes, a newtoplevel tree object is created.  See\n> +`OUTPUT` below for details.\n>\n>  OPTIONS\n>  -------\n> @@ -54,7 +45,7 @@ OPTIONS\n>         Do not quote filenames in the <Conflicted file info> section,\n>         and end each filename with a NUL character rather than\n>         newline.  Also begin the messages section with a NUL character\n> -       instead of a newline.  See OUTPUT below for more information.\n> +       instead of a newline.  See <<OUTPUT>> below for more information.\n>\n>  --name-only::\n>         In the Conflicted file info section, instead of writing a list\n> @@ -74,11 +65,12 @@ OPTIONS\n>         share no common history.  This flag can be given to override that\n>         check and make the merge proceed anyway.\n>\n> +[[OUTPUT]]\n>  OUTPUT\n>  ------\n>\n> -By default, for a successful merge, the output from git-merge-tree is\n> -simply one line:\n> +For a successful merge, the output from git-merge-tree is simply one\n> +line:\n>\n>         <OID of toplevel tree>\n>\n> @@ -90,6 +82,7 @@ Whereas for a conflicted merge, the output is by default of the form:\n>\n>  These are discussed individually below.\n>\n> +[[OIDTLT]]\n>  OID of toplevel tree\n>  ~~~~~~~~~~~~~~~~~~~~\n>\n> @@ -98,6 +91,7 @@ working tree at the end of `git merge`.  If there were conflicts, then\n>  files within this tree may have embedded conflict markers.  This section\n>  is always followed by a newline (or NUL if `-z` is passed).\n>\n> +[[CFI]]\n>  Conflicted file info\n>  ~~~~~~~~~~~~~~~~~~~~\n>\n> @@ -111,6 +105,7 @@ the `--name-only` option is passed, the mode, object, and stage will\n>  be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n>  character instead of a newline character.\n>\n> +[[IM]]\n>  Informational messages\n>  ~~~~~~~~~~~~~~~~~~~~~~\n>\n> @@ -138,72 +133,94 @@ something other than 0 or 1 (and the output is unspecified).\n>  USAGE NOTES\n>  -----------\n>\n> -git-merge-tree was written to be low-level plumbing, similar to\n> -hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n> -Thus, it could be used as a part of a series of steps such as\n> +This command is intended as low-level plumbing, similar to\n> +linkgit:git-hash-object[1], linkgit:git-mktree[1],\n> +linkgit:git-commit-tree[1], linkgit:git-write-tree[1],\n> +linkgit:git-update-ref[1], and linkgit:git-mktag[1].  Thus, it can be\n> +used as a part of a series of steps such as:\n>\n>         NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n>         test $? -eq 0 || die \"There were conflicts...\"\n>         NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n>         git update-ref $BRANCH1 $NEWCOMMIT\n>\n> -Note that when the exit status is non-zero, NEWTREE in this sequence\n> +Note that when the exit status is non-zero, `NEWTREE` in this sequence\n>  will contain a lot more output than just a tree.\n>\n> -git-merge-tree was written to provide users with the same information\n> -that they'd have access to if using `git merge`:\n> -  * what would be written to the working tree (the <OID of toplevel tree>)\n> +The output will include the same information that you'd get with\n> +linkgit:git-merge[1]:\n> +\n> +  * what would be written to the working tree (the <<OIDTLT,OID of toplevel tree>>)\n>    * the higher order stages that would be written to the index (the\n> -    <Conflicted file info>)\n> -  * any messages that would have been printed to stdout (the <Informational\n> -    messages>)\n> +    <<CFI,Conflicted file info>>)\n> +  * any messages that would have been printed to stdout (the <<IM,Informational\n> +    messages>>)\n>\n>  MISTAKES TO AVOID\n>  -----------------\n>\n>  Do NOT look through the resulting toplevel tree to try to find which\n> -files conflict; parse the <Conflicted file info> section instead.  Not\n> +files conflict; parse the <<CFI,Conflicted file info>> section instead.  Not\n>  only would parsing an entire tree be horrendously slow in large\n>  repositories, there are numerous types of conflicts not representable by\n>  conflict markers (modify/delete, mode conflict, binary file changed on\n>  both sides, file/directory conflicts, various rename conflict\n>  permutations, etc.)\n>\n> -Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n> +Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean merge;\n>  check the exit status.  A merge can have conflicts without having\n>  individual files conflict (there are a few types of directory rename\n>  conflicts that fall into this category, and others might also be added\n>  in the future).\n>\n>  Do NOT attempt to guess or make the user guess the conflict types from\n> -the <Conflicted file info> list.  The information there is insufficient\n> +the <<CFI,Conflicted file info>> list.  The information there is insufficient\n>  to do so.  For example: Rename/rename(1to2) conflicts (both sides\n>  renamed the same file differently) will result in three different file\n>  having higher order stages (but each only has one higher order stage),\n> -with no way (short of the <Informational messages> section) to determine\n> +with no way (short of the <<IM,Informational messages>> section) to determine\n>  which three files are related.  File/directory conflicts also result in\n>  a file with exactly one higher order stage.\n>  Possibly-involved-in-directory-rename conflicts (when\n>  \"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n>  a file with exactly one higher order stage.  In all cases, the\n> -<Informational messages> section has the necessary info, though it is\n> +<<IM,Informational messages>> section has the necessary info, though it is\n>  not designed to be machine parseable.\n>\n> -Do NOT assume all filenames listed in the <Informational messages>\n> +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n>  section had conflicts.  Messages can be included for files that have no\n>  conflicts, such as \"Auto-merging <file>\".\n>\n> -AVOID taking the OIDS from the <Conflicted file info> and re-merging\n> +AVOID taking the OIDS from the <<CFI,Conflicted file info>> and re-merging\n>  them to present the conflicts to the user.  This will lose information.\n> -Instead, look up the version of the file found within the <OID of\n> -toplevel tree> and show that instead.  In particular, the latter will\n> +Instead, look up the version of the file found within the <<OIDTLT,OID of\n> +toplevel tree>> and show that instead.  In particular, the latter will\n>  have conflict markers annotated with the original branch/commit being\n>  merged and, if renames were involved, the original filename.  While you\n>  could include the original branch/commit in the conflict marker\n>  annotations when re-merging, the original filename is not available from\n> -the <Conflicted file info> and thus you would be losing information that\n> +the <<CFI,Conflicted file info>> and thus you would be losing information that\n>  might help the user resolve the conflict.\n>\n> +[[DEPMERGE]]\n> +DEPRECATED DESCRIPTION\n> +----------------------\n> +\n> +Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this\n> +documentation this section describes describes the deprecated\n> +`--trivial-merge` mode.\n> +\n> +Reads three tree-ish, and output trivial merge results and\n> +conflicting stages to the standard output.  This is similar to\n> +what three-way 'git read-tree -m' does, but instead of storing the\n> +results in the index, the command outputs the entries to the\n> +standard output.\n> +\n> +This is meant to be used by higher level scripts to compute\n> +merge results outside of the index, and stuff the results back into the\n> +index.  For this reason, the output from the command omits\n> +entries that match the <branch1> tree.\n\nWhat you have in the new section is fine, but what you've deleted\nshould probably be included here.  In particular, the brief\nexplanation about why this mode is deprecated.\n\n\n> +\n>  GIT\n>  ---\n>  Part of the linkgit:git[1] suite\n>\n"},{"id":"449086","messageId":"CABPp-BESAh6wLComJoYsf7Q7NF2EMPptKRhfAoy=1-ZRZovnaQ@mail.gmail.com","threadId":"57288","inReplyTo":"9b65e743-729f-6449-b7ef-c8c9fb130221@web.de","subject":"Re: [PATCH v5 04/12] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-22T02:28:27Z","receivedAt":"2022-02-22T02:28:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sun, Feb 20, 2022 at 1:03 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> Am 20.02.22 um 07:54 schrieb Elijah Newren via GitGitGadget:\n[...]\n> > +     /*\n> > +      * Get the merge bases, in reverse order; see comment above\n> > +      * merge_incore_recursive in merge-ort.h\n> > +      */\n> > +     common = get_merge_bases(parent1, parent2);\n> > +     if (!common)\n> > +             die(_(\"refusing to merge unrelated histories\"));\n> > +     for (j = common; j; j = j->next)\n> > +             commit_list_insert(j->item, &merge_bases);\n>\n> This loop creates a reversed copy of \"common\".  You could use\n> reverse_commit_list() instead to do it in-place and avoid the\n> allocations.  Only the copy, \"merge_bases\", is used below.\n\nOh, good catch.  I probably should have been aware of this since\nsomeone requested I move the reverse_commit_list() function from\nmerge-recursive.c to commit.c as part of my merge-ort work, but looks\nlike I forgot about it and copied this command snippet from\nbuiltin/merge.c instead.  I have no excuse.\n\nHowever, I wonder if that means we could also apply this\nsimplification to the code snippets in builtin/merge.c and sequencer.c\nthat you can find with\n    git grep commit_list_insert.*reversed\n?  Maybe #leftoverbits for that part?\n"},{"id":"449088","messageId":"CABPp-BFK_7eFtJ5C-fqLCvpacOj-AEmy=+6jteYWcY0X4BRKgg@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202210956430.26495@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-22T02:37:26Z","receivedAt":"2022-02-22T02:37:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 21, 2022 at 1:06 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi,\n>\n> On Thu, 3 Feb 2022, Elijah Newren wrote:\n\n[...]\n> > I agree.  I've got a lot of thoughts on it, and some work in progress\n> > towards it (https://github.com/newren/git/tree/replay -- _very_ hacky,\n> > not even close to alpha quality, lots of fixup commits, todo comments,\n> > random brain dump files added to the tree, based on a previous round\n> > of this patch series, not updated for weeks, etc., etc.)\n>\n> Just chiming in that I find that very exciting. But it's a tangent, and\n> slightly distracting from the topic at hand, so I would like to ask to\n> focus back on server-side merges.\n\n+1 for refocusing.\n\n> > We'll certainly have discussions on what that should look like.  But a\n> > plumbing-ish replacement for merge was much simpler, and made sense to\n> > do first.  I would prefer to concentrate on getting that hammered down\n> > first.  Then I'll start discussions on a plumbing-ish\n> > rebase/cherry-pick.  And if that doesn't fulfill all the needs that\n> > folks think they want out of merge-tree, then we can add a\n> > merge_incore_nonrecursive()-based mode to merge-tree.  It's all\n> > coming, but having fought transliterations-of-scripts in\n> > merge-recursive.c, sequencer.c, stash.c, rebase.c, etc. for years I\n> > really, really don't want any more of that.  Let's end that insanity.\n>\n> Being the driving force behind many a \"built-in-ification\" of scripted\n> commands, I wholeheartedly agree. You can still see the fall-out of\n> designing commands in a scripted fashion, without any way to represent\n> data structures other than strings. I wish we had come up with a better\n> design to prototype commands than to write shell scripts. But I have to\n> admit that even I do not have any better idea than to work on a proper API\n> for libgit.a (which has historically invariably seen push-back from\n> Junio).\n>\n> While I agree that this discussion is a valuable one, right now I would\n> like to focus on getting the server-side merges done, and once that has\n> happened, move on to the replay/sequencer/API discussion (which will\n> probably be a big one, not so much for technical reasons but more for all\n> too human ones).\n\nI'm just guessing, but I suspect your prediction for the future\nreplay/sequencer/rebase/API discussion will be spot on.  :-)\n"},{"id":"449096","messageId":"220222.86sfsb8b5f.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BF0LtkDPxBY7ECbS4110-38sa=YXBkUkjw6mNFH4PEebQ@mail.gmail.com","subject":"Re: [PATCH v5 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-22T10:07:21Z","receivedAt":"2022-02-22T10:10:38Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Feb 21 2022, Elijah Newren wrote:\n\n> On Sun, Feb 20, 2022 at 4:35 AM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>> On Sun, Feb 20 2022, Elijah Newren via GitGitGadget wrote:\n>>\n>> > == Updates Log ==\n>> >\n>> > Many thanks to the many reviewers who provided good feedback on the most\n>> > recent round -- Junio, Ævar, Josh, Emily, and perhaps some others I've\n>> > forgotten from review club.\n>> >\n>> > Updates since v4:\n>> >\n>> >  * Fixed double \"is\" in documentation.\n>> >  * Fixed a few small items with testcases\n>> >\n>> > Updates since v3 (or v5, if you include the rounds at\n>> > https://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/):\n>> >\n>> >  * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n>> >    being used and opened a can of worms[1]\n>> >  * [Patch 3] Restructured argument checking, including using an enum\n>> >  * [Patch 4] Restored the extended paragraph about the deprecated form of\n>> >    git-merge-tree, mentioned write-tree in plumbing commands, and a few\n>> >    other small fixups to the documentation\n>> >  * [Patch 4] Also provide an example of a clean merge rather than just a\n>> >    conflicted one\n>> >  * [Patch 6] Fix the incompatible arguments check and add some tests for it\n>> >  * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n>> >    easier to read (less repeated sed)\n>> >  * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n>> >    option for now\n>> >  * [Patch 10] When -z passed, the tree in the first section should have a\n>> >    trailing NUL rather than trailing newline [1]\n>> >    https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n>> >\n>> > Stuff NOT included that reviewers brought up in earlier rounds:\n>> >\n>> >  * Very generic (mode, oid, stage, filename) printing formatting[2]\n>> >  * Always printing 3 stages for each filename with conflicts[3]\n>> >  * Attempting to group conflict stages by logical conflict rather than by\n>> >    affected target filepath[4]\n>> >  * Providing similar functionality for doing cherry-picks/rebases/reverts,\n>> >    i.e. a scheme for three-way merges with a specified merge-base[5]. That's\n>> >    being deferred to a future series. [2]\n>> >    https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n>> >    [3]\n>> >    https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/\n>> >    [4]\n>> >    https://lore.kernel.org/git/CABPp-BGCL0onSmpgKuO1k2spYCkx=v27ed9TSSxFib=OdDcLbw@mail.gmail.com/\n>> >    [5]\n>> >    https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n>>\n>> I've looked through this, I think it all looks good overall & that the\n>> things that needed to be addressed (as opposed to my --format rambling)\n>> have been.\n>>\n>> I think all the code should be ready for \"next\".\n>>\n>> I suggested (I think around getopts discussion) in an earlier that the\n>> code would have been easier with a new built-in, but if we're\n>> deprecating the existing \"mode\" I think using the name is probably\n>> better in the end.\n>>\n>> I find the resulting documentation to be really hard to grok though\n>> because we're effectively describing two different commands. The current\n>> docs are small: https://git-scm.com/docs/git-merge-tree\n>>\n>> I built the tip of this series and read the manpage, and found myself\n>> needing to carefully squint to see what referred to what mode in the\n>> docs.\n>>\n>> E.g. by the time it's discussing \"-z\" and other options the reader needs\n>> to be astute really aware of the context, and infer from the lack of\n>> \"<options>\" on the \"--trivial-merge\" that these options refer to the\n>> \"--write-tree\" only.\n>>\n>> The same goes for the rest of \"--trivial-merge\". I.e. I found myself\n>> needing to read the whole docs word-by-word (no skimming!) to see if\n>> OUTPUT etc. was going to describe its output, or just the \"new\" mode.\n>>\n>> It is my NSHO that man pages should be structured for the impatient\n>> reader :)\n>>\n>> Then when you say \"git-merge-tree was written to be[...]\" I thought \"ah\n>> ha! surely this will discuss the since-2005 implemented mode\", but \"was\n>> written to be\" is referring to code new in this series.\n>>\n>> The below patch-on-top addresses all those concerns. Basically I just\n>> added a line to the top of the DESCRIPTION saying that you should read a\n>> \"DEPRECATED DESCRIPTION\" section at the end for \"--trivial-merge\", and\n>> that all of the rest is talking about the \"--write-tree\" mode.\n>>\n>> I then edited various prose to do away with the now-unnecessary \"the\n>> first form\" etc.\n>>\n>> This diff is better against \"master\" in that you'll see that the current\n>> merge-tree DESCRIPTION section isn't touched at all (it's now just under\n>> a new heading), but this diff is against the tip of your series.\n>>\n>> There's various other small fixes while I was it it here, e.g. all your\n>> cross-section links were using some pseudo-not-quite-ASCIIDOC syntax\n>> that doesn't work. Now it uses the right syntax. Ditto link-ifying\n>> references to \"mktag\" etc.\n>>\n>> I don't know if you'd consider this for a v6, or if I should just submit\n>> this on top myself, but in any case here it is. I'll leave it to you how\n>> you'd like to proceed with it:\n>\n> Overall the diff below looks good; thanks!  I'll include it in a v6.\n> Two exceptions, listed below:\n\nThanks.\n\n>> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n>> index 306149fa0e2..723b1995426 100644\n>> --- a/Documentation/git-merge-tree.txt\n>> +++ b/Documentation/git-merge-tree.txt\n>> @@ -9,17 +9,24 @@ git-merge-tree - Perform merge without touching index or working tree\n>>  SYNOPSIS\n>>  --------\n>>  [verse]\n>> -'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n>> -'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n>> +'git merge-tree' --write-tree [<options>] <branch1> <branch2>\n>> +'git merge-tree' --trivial-merge <base-tree> <branch1> <branch2>\n>\n> I dislike all three changes to the synopsis; I'll discard these.\n\nI don't know why I changed --write-tree to [--write-tree], I think\nthat's just incorrect.\n\nBut what I was going for was removing \"(deprecated)\" there.\n\nMaybe it's confusing to noone else, but it is ambiguous in the way we\nusually use parens in SYNOPSIS. I.e. per that syntax it means that the\nstring \"deprecated\" is a mandotory paramater after <branch2>.\n\nMaybe just having a \"DEPRECATED SYNOPSIS\" section after \"SYNOPSIS\" would\nbe better? That would also allow for more lines of examples without\nre-wording everything that comes after with \"the first form\" etc.\n\n>> +[[NEWMERGE]]\n>>  DESCRIPTION\n>>  -----------\n>>\n>> -Performs a merge, but does not make any new commits and does not read\n>> -from or write to either the working tree or index.\n>> +This command has a modern `--write-tree` mode and a deprecated\n>> +`--trivial-merge` mode. The rest of this documentation describes\n>> +modern `--write-tree` mode unless otherwise specified. see\n>> +<<DEPMERGE,DEPRECATED DESCRIPTION>> below for a summary of the\n>> +`--trivial-merge` mode.\n>>\n>> -The first form will merge the two branches, doing a real merge.  A real\n>> -merge is distinguished from a trivial merge in that it includes:\n>> +Performs a \"real\" merge, but does not make any new commits and does\n>> +not read from or write to either the working tree or index.\n>> +\n>> +The performed merge will use the same feature as the \"real\"\n>> +linkgit:git-merge[1], including:\n>>\n>>    * three way content merges of individual files\n>>    * rename detection\n>> @@ -28,24 +35,8 @@ merge is distinguished from a trivial merge in that it includes:\n>>      merge base, creating a virtual merge base by merging the merge bases)\n>>    * etc.\n>>\n>> -After the merge completes, the first form will create a new toplevel\n>> -tree object.  See `OUTPUT` below for details.\n>> -\n>> -The second form is deprecated; it is kept for backward compatibility\n>> -reasons but may be deleted in the future.  Other than the optional\n>> -`--trivial-merge`, it accepts no options.  It can only do a trivial\n>> -merge.  It reads three tree-ish, and outputs trivial merge results and\n>> -conflicting stages to the standard output in a semi-diff format.\n>> -Since this was designed for higher level scripts to consume and merge\n>> -the results back into the index, it omits entries that match\n>> -<branch1>.  The result of this second form is similar to what\n>> -three-way 'git read-tree -m' does, but instead of storing the results\n>> -in the index, the command outputs the entries to the standard output.\n>> -This form not only has limited applicability, the output format is\n>> -also difficult to work with, and it will generally be less performant\n>> -than the first form even on successful merges (especially if working\n>> -in large repositories).  The remainder of this manual will only\n>> -discuss the first form.\n>> +After the merge completes, a newtoplevel tree object is created.  See\n>> +`OUTPUT` below for details.\n>>\n>>  OPTIONS\n>>  -------\n>> @@ -54,7 +45,7 @@ OPTIONS\n>>         Do not quote filenames in the <Conflicted file info> section,\n>>         and end each filename with a NUL character rather than\n>>         newline.  Also begin the messages section with a NUL character\n>> -       instead of a newline.  See OUTPUT below for more information.\n>> +       instead of a newline.  See <<OUTPUT>> below for more information.\n>>\n>>  --name-only::\n>>         In the Conflicted file info section, instead of writing a list\n>> @@ -74,11 +65,12 @@ OPTIONS\n>>         share no common history.  This flag can be given to override that\n>>         check and make the merge proceed anyway.\n>>\n>> +[[OUTPUT]]\n>>  OUTPUT\n>>  ------\n>>\n>> -By default, for a successful merge, the output from git-merge-tree is\n>> -simply one line:\n>> +For a successful merge, the output from git-merge-tree is simply one\n>> +line:\n>>\n>>         <OID of toplevel tree>\n>>\n>> @@ -90,6 +82,7 @@ Whereas for a conflicted merge, the output is by default of the form:\n>>\n>>  These are discussed individually below.\n>>\n>> +[[OIDTLT]]\n>>  OID of toplevel tree\n>>  ~~~~~~~~~~~~~~~~~~~~\n>>\n>> @@ -98,6 +91,7 @@ working tree at the end of `git merge`.  If there were conflicts, then\n>>  files within this tree may have embedded conflict markers.  This section\n>>  is always followed by a newline (or NUL if `-z` is passed).\n>>\n>> +[[CFI]]\n>>  Conflicted file info\n>>  ~~~~~~~~~~~~~~~~~~~~\n>>\n>> @@ -111,6 +105,7 @@ the `--name-only` option is passed, the mode, object, and stage will\n>>  be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n>>  character instead of a newline character.\n>>\n>> +[[IM]]\n>>  Informational messages\n>>  ~~~~~~~~~~~~~~~~~~~~~~\n>>\n>> @@ -138,72 +133,94 @@ something other than 0 or 1 (and the output is unspecified).\n>>  USAGE NOTES\n>>  -----------\n>>\n>> -git-merge-tree was written to be low-level plumbing, similar to\n>> -hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n>> -Thus, it could be used as a part of a series of steps such as\n>> +This command is intended as low-level plumbing, similar to\n>> +linkgit:git-hash-object[1], linkgit:git-mktree[1],\n>> +linkgit:git-commit-tree[1], linkgit:git-write-tree[1],\n>> +linkgit:git-update-ref[1], and linkgit:git-mktag[1].  Thus, it can be\n>> +used as a part of a series of steps such as:\n>>\n>>         NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n>>         test $? -eq 0 || die \"There were conflicts...\"\n>>         NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n>>         git update-ref $BRANCH1 $NEWCOMMIT\n>>\n>> -Note that when the exit status is non-zero, NEWTREE in this sequence\n>> +Note that when the exit status is non-zero, `NEWTREE` in this sequence\n>>  will contain a lot more output than just a tree.\n>>\n>> -git-merge-tree was written to provide users with the same information\n>> -that they'd have access to if using `git merge`:\n>> -  * what would be written to the working tree (the <OID of toplevel tree>)\n>> +The output will include the same information that you'd get with\n>> +linkgit:git-merge[1]:\n>> +\n>> +  * what would be written to the working tree (the <<OIDTLT,OID of toplevel tree>>)\n>>    * the higher order stages that would be written to the index (the\n>> -    <Conflicted file info>)\n>> -  * any messages that would have been printed to stdout (the <Informational\n>> -    messages>)\n>> +    <<CFI,Conflicted file info>>)\n>> +  * any messages that would have been printed to stdout (the <<IM,Informational\n>> +    messages>>)\n>>\n>>  MISTAKES TO AVOID\n>>  -----------------\n>>\n>>  Do NOT look through the resulting toplevel tree to try to find which\n>> -files conflict; parse the <Conflicted file info> section instead.  Not\n>> +files conflict; parse the <<CFI,Conflicted file info>> section instead.  Not\n>>  only would parsing an entire tree be horrendously slow in large\n>>  repositories, there are numerous types of conflicts not representable by\n>>  conflict markers (modify/delete, mode conflict, binary file changed on\n>>  both sides, file/directory conflicts, various rename conflict\n>>  permutations, etc.)\n>>\n>> -Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n>> +Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean merge;\n>>  check the exit status.  A merge can have conflicts without having\n>>  individual files conflict (there are a few types of directory rename\n>>  conflicts that fall into this category, and others might also be added\n>>  in the future).\n>>\n>>  Do NOT attempt to guess or make the user guess the conflict types from\n>> -the <Conflicted file info> list.  The information there is insufficient\n>> +the <<CFI,Conflicted file info>> list.  The information there is insufficient\n>>  to do so.  For example: Rename/rename(1to2) conflicts (both sides\n>>  renamed the same file differently) will result in three different file\n>>  having higher order stages (but each only has one higher order stage),\n>> -with no way (short of the <Informational messages> section) to determine\n>> +with no way (short of the <<IM,Informational messages>> section) to determine\n>>  which three files are related.  File/directory conflicts also result in\n>>  a file with exactly one higher order stage.\n>>  Possibly-involved-in-directory-rename conflicts (when\n>>  \"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n>>  a file with exactly one higher order stage.  In all cases, the\n>> -<Informational messages> section has the necessary info, though it is\n>> +<<IM,Informational messages>> section has the necessary info, though it is\n>>  not designed to be machine parseable.\n>>\n>> -Do NOT assume all filenames listed in the <Informational messages>\n>> +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n>>  section had conflicts.  Messages can be included for files that have no\n>>  conflicts, such as \"Auto-merging <file>\".\n>>\n>> -AVOID taking the OIDS from the <Conflicted file info> and re-merging\n>> +AVOID taking the OIDS from the <<CFI,Conflicted file info>> and re-merging\n>>  them to present the conflicts to the user.  This will lose information.\n>> -Instead, look up the version of the file found within the <OID of\n>> -toplevel tree> and show that instead.  In particular, the latter will\n>> +Instead, look up the version of the file found within the <<OIDTLT,OID of\n>> +toplevel tree>> and show that instead.  In particular, the latter will\n>>  have conflict markers annotated with the original branch/commit being\n>>  merged and, if renames were involved, the original filename.  While you\n>>  could include the original branch/commit in the conflict marker\n>>  annotations when re-merging, the original filename is not available from\n>> -the <Conflicted file info> and thus you would be losing information that\n>> +the <<CFI,Conflicted file info>> and thus you would be losing information that\n>>  might help the user resolve the conflict.\n>>\n>> +[[DEPMERGE]]\n>> +DEPRECATED DESCRIPTION\n>> +----------------------\n>> +\n>> +Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this\n>> +documentation this section describes describes the deprecated\n>> +`--trivial-merge` mode.\n>> +\n>> +Reads three tree-ish, and output trivial merge results and\n>> +conflicting stages to the standard output.  This is similar to\n>> +what three-way 'git read-tree -m' does, but instead of storing the\n>> +results in the index, the command outputs the entries to the\n>> +standard output.\n>> +\n>> +This is meant to be used by higher level scripts to compute\n>> +merge results outside of the index, and stuff the results back into the\n>> +index.  For this reason, the output from the command omits\n>> +entries that match the <branch1> tree.\n>\n> What you have in the new section is fine, but what you've deleted\n> should probably be included here.  In particular, the brief\n> explanation about why this mode is deprecated.\n\nAh, yes. That was overzelous. Sorry. I skimmed that and IIRC thought it\nwas a re-flowing of the old DESCRIPTION section.\n"},{"id":"449149","messageId":"nycvar.QRO.7.76.6.2202221720360.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BESAh6wLComJoYsf7Q7NF2EMPptKRhfAoy=1-ZRZovnaQ@mail.gmail.com","subject":"Re: [PATCH v5 04/12] merge-tree: implement real merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-22T16:25:23Z","receivedAt":"2022-02-22T16:25:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Mon, 21 Feb 2022, Elijah Newren wrote:\n\n> On Sun, Feb 20, 2022 at 1:03 AM René Scharfe <l.s.r@web.de> wrote:\n> >\n> > Am 20.02.22 um 07:54 schrieb Elijah Newren via GitGitGadget:\n> [...]\n> > > +     /*\n> > > +      * Get the merge bases, in reverse order; see comment above\n> > > +      * merge_incore_recursive in merge-ort.h\n> > > +      */\n> > > +     common = get_merge_bases(parent1, parent2);\n> > > +     if (!common)\n> > > +             die(_(\"refusing to merge unrelated histories\"));\n> > > +     for (j = common; j; j = j->next)\n> > > +             commit_list_insert(j->item, &merge_bases);\n> >\n> > This loop creates a reversed copy of \"common\".  You could use\n> > reverse_commit_list() instead to do it in-place and avoid the\n> > allocations.  Only the copy, \"merge_bases\", is used below.\n>\n> Oh, good catch.  I probably should have been aware of this since\n> someone requested I move the reverse_commit_list() function from\n> merge-recursive.c to commit.c as part of my merge-ort work, but looks\n> like I forgot about it and copied this command snippet from\n> builtin/merge.c instead.  I have no excuse.\n\nOoops! I missed that the `reverse_commit_list()` function was moved to\n`commit.c` by _you_, and had not been there all along (my fault, of\ncourse, see 8918b0c9c26 (merge-recur: try to merge older merge bases\nfirst, 2006-08-09)).\n\n> However, I wonder if that means we could also apply this\n> simplification to the code snippets in builtin/merge.c and sequencer.c\n> that you can find with\n>     git grep commit_list_insert.*reversed\n> ?  Maybe #leftoverbits for that part?\n\nYes, that's a good idea. I summarized this left-over-bit in\nhttps://github.com/gitgitgadget/git/issues/1156\n\nCiao,\nDscho\n"},{"id":"449150","messageId":"nycvar.QRO.7.76.6.2202221725430.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"7abf633b6382118a63e983b80186e91dc38eef5f.1645340082.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 12/12] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-22T16:26:29Z","receivedAt":"2022-02-22T16:26:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sun, 20 Feb 2022, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n>  1 file changed, 46 insertions(+)\n\nThank you for this. It addresses the concern I had about printing out the\ntree object ID in the conflicted case.\n\nThanks,\nDscho\n\n>\n> diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\n> index d2ff2fa3035..306149fa0e2 100644\n> --- a/Documentation/git-merge-tree.txt\n> +++ b/Documentation/git-merge-tree.txt\n> @@ -158,6 +158,52 @@ that they'd have access to if using `git merge`:\n>    * any messages that would have been printed to stdout (the <Informational\n>      messages>)\n>\n> +MISTAKES TO AVOID\n> +-----------------\n> +\n> +Do NOT look through the resulting toplevel tree to try to find which\n> +files conflict; parse the <Conflicted file info> section instead.  Not\n> +only would parsing an entire tree be horrendously slow in large\n> +repositories, there are numerous types of conflicts not representable by\n> +conflict markers (modify/delete, mode conflict, binary file changed on\n> +both sides, file/directory conflicts, various rename conflict\n> +permutations, etc.)\n> +\n> +Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n> +check the exit status.  A merge can have conflicts without having\n> +individual files conflict (there are a few types of directory rename\n> +conflicts that fall into this category, and others might also be added\n> +in the future).\n> +\n> +Do NOT attempt to guess or make the user guess the conflict types from\n> +the <Conflicted file info> list.  The information there is insufficient\n> +to do so.  For example: Rename/rename(1to2) conflicts (both sides\n> +renamed the same file differently) will result in three different file\n> +having higher order stages (but each only has one higher order stage),\n> +with no way (short of the <Informational messages> section) to determine\n> +which three files are related.  File/directory conflicts also result in\n> +a file with exactly one higher order stage.\n> +Possibly-involved-in-directory-rename conflicts (when\n> +\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n> +a file with exactly one higher order stage.  In all cases, the\n> +<Informational messages> section has the necessary info, though it is\n> +not designed to be machine parseable.\n> +\n> +Do NOT assume all filenames listed in the <Informational messages>\n> +section had conflicts.  Messages can be included for files that have no\n> +conflicts, such as \"Auto-merging <file>\".\n> +\n> +AVOID taking the OIDS from the <Conflicted file info> and re-merging\n> +them to present the conflicts to the user.  This will lose information.\n> +Instead, look up the version of the file found within the <OID of\n> +toplevel tree> and show that instead.  In particular, the latter will\n> +have conflict markers annotated with the original branch/commit being\n> +merged and, if renames were involved, the original filename.  While you\n> +could include the original branch/commit in the conflict marker\n> +annotations when re-merging, the original filename is not available from\n> +the <Conflicted file info> and thus you would be losing information that\n> +might help the user resolve the conflict.\n> +\n>  GIT\n>  ---\n>  Part of the linkgit:git[1] suite\n> --\n> gitgitgadget\n>\n"},{"id":"449151","messageId":"CABPp-BE+DaBkis0r7pqs-kaChCvFhCEsyDg=gs3=QjWOPERaXQ@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqee3wt5g3.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-22T16:26:41Z","receivedAt":"2022-02-22T16:27:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 21, 2022 at 10:55 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Adding such an ability to merge-tree would be trivial -- it basically\n> > involves just two things: (1) accepting one extra argument, and (2)\n> > calling merge_incore_nonrecursive() instead of\n> > merge_incore_recursive().\n> >\n> > However, I think forking a subprocess for every merge of a series of\n> > commits is a completely unreasonable overhead, so even if we provide\n> > such an option to merge-tree, I still want a separate plumbing-ish\n> > tool that does non-worktree/non-index replaying of commits which is\n> > not written as a driver of merge-tree.  That other tool should just\n> > call merge_incore_nonrecursive() directly.  And such a tool, since it\n> > should handle an arbitrary number of commits, should certainly be able\n> > to handle just one commit.  From that angle, it feels like adding\n> > another mode to merge-tree would just be a partial duplication of the\n> > other tool.\n>\n> The above does not make much sense to me.\n>\n> I am hearing that \"multi-step cherry-picks and reverts need to be\n> fast and we need something like sequencer that is all written in C,\n\nYes, I agree with that part so far.  jj is kicking our butt on rebase\nspeed; I'm not sure if we can catch it, but it'd be nice to see us not\nbe more than a hundred times slower.\n\n> and single-step cherry-pick is merely a special case that does not\n> deserve a plumbing\".\n\nWell, apparently I failed at communication if that's what you heard.\nPerhaps I can step back and provide my high-level goals, and then\nmention how this series fits in.  My high-level goals:\n\n  * new sequencer-like replay tool, including multiple abilities\ntoday's rebase/cherry-pick tools don't have\n  * enable folks to use merging machinery for server side operations\n(merge, rebase, cherry-pick, revert)\n  * do not repeat or encourage the rebase-as-shell-script mistakes of yesteryear\n  * somehow split this up into reviewable chunks\n\nNow, in particular, the \"merge divergent branches\" piece seemed like a\nreally simple portion of the problem space for which I could get some\nearly feedback without having to address the whole problem space all\nat once, and which doesn't seem to have any downside risk.\n\nAnd even with my attempt to narrow it in scope, and even despite lots\nof early feedback from the Git Virtual Contributor Summit six months\nago, it's been nearly two months of active discussions including all\nkinds of intrinsic and tangential points about the UI and design.  Why\ntry to prematurely widen the scope?  Can we just focus on merging\ndivergent branches for now, and cover the rest later?\n\n> But that argument leads to \"and the same something-like-sequencer\n> that is all written in C would need '--rebase-merges' that can pick\n> multi-step merge sequences, and single-step merge does not deserve a\n> plumbing\", which is an argument against this topic that is utterly\n> absurd.\n>\n> So why isn't your objection not equally absurd against having a\n> single step cherry-pick or revert primitive as a plumbing?\n\nThe objection you are arguing against is not my position.  In fact,\nI'm not even objecting to having a single-step cherry-pick, I'm\nobjecting to providing it _now_, which I thought would have been clear\nfrom the portion of my email you snipped (\"...I'm happy to add [a\nsingle step cherry-pick primitive] along with the tool I submit\nlater...\").  Since that wasn't clear, and since that wasn't my only\ncommunication failure here, let me attempt to be clearer about my\nobjection(s):\n\n1. I'm really trying to pick off a small piece of the problem space\nand get feedback on it without unnecessarily complicating things with\nunrelated issues.  Thus, this series is _only_ about merging branches\nthat have diverged, and leaves commit replaying for later.\n\n2. Two folks have chimed in about the single step cherry-pick, and the\nONLY reason given for wanting such a thing was to create a\nrebasing/cherry-picking script which was driven by repeatedly invoking\nthis low-level primitive command.  That's also the only usecase I can\ncurrently think of for such a primitive.  To me, that means providing\nsuch a low-level command now would be likely to result in the\nrebase-as-a-script mistake of yesteryear.  I think we can avoid that\npitfall by first providing a tool that avoids the\nrepeatedly-fork-git-subprocesses model.  (Also, providing a low-level\nsingle-step cherry-pick command also has the added negative of further\ndistracting from the focus on merging divergent branches.)\n\n3. The merge primitive in this series is useful completely independent\nof any rebasing script (it would not be used solely for rebasing\nmerges, if it's used for that purpose at all, as evidenced by the fact\nthat dscho is already trying to use it for doing new real merges).\n\n4. Once we have a git-replay tool that can replay a sequence of\ncommits, there _might_ not be a need for a single commit replaying\nprimitive.  If we provided one as you and Johannes Altimanninger were\nasking for, and it turned out to be deemed useless because the later\ntool I provide can do everything it can and more, haven't we just\nwasted time in providing it?  And perhaps also wasted future time as\nwe then have work to do to deprecate and remove the new command or\nmode? (NOTE: I did *not* say there was \"no need\" for a single-commit\nreplaying primitive -- I said there \"might not\" be a need.)\n\nAlso, since you bring up --rebase-merges, there's an additional point\nabout it that might be relevant:\n\n5. While you could implement a naive --rebase-merges in terms of a\nprimitive for merging divergent branches (or vice-versa, i.e.\nimplement merging divergent branches from a naive --rebase-merges\nimplementation), I think replaying merges more intelligently[*] is\nactually a distinct operation from doing a new merge of divergent\nbranches and that you probably can't implement one in terms of the\nother.  (I'm not certain on this, and definitely don't want to argue\nthe finer points on it while my implementation is still half-baked,\nbut I really do think they are different things right now.)\n\n[*] https://lore.kernel.org/git/CABPp-BHp+d62dCyAaJfh1cZ8xVpGyb97mZryd02aCOX=Qn=Ltw@mail.gmail.com/\n"},{"id":"449168","messageId":"nycvar.QRO.7.76.6.2202221730431.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"xmqqee3wt5g3.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-22T16:45:55Z","receivedAt":"2022-02-22T16:46:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Mon, 21 Feb 2022, Junio C Hamano wrote:\n\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > Adding such an ability to merge-tree would be trivial -- it basically\n> > involves just two things: (1) accepting one extra argument, and (2)\n> > calling merge_incore_nonrecursive() instead of\n> > merge_incore_recursive().\n> >\n> > However, I think forking a subprocess for every merge of a series of\n> > commits is a completely unreasonable overhead, so even if we provide\n> > such an option to merge-tree, I still want a separate plumbing-ish\n> > tool that does non-worktree/non-index replaying of commits which is\n> > not written as a driver of merge-tree.  That other tool should just\n> > call merge_incore_nonrecursive() directly.  And such a tool, since it\n> > should handle an arbitrary number of commits, should certainly be able\n> > to handle just one commit.  From that angle, it feels like adding\n> > another mode to merge-tree would just be a partial duplication of the\n> > other tool.\n>\n> The above does not make much sense to me.\n>\n> I am hearing that \"multi-step cherry-picks and reverts need to be\n> fast and we need something like sequencer that is all written in C,\n> and single-step cherry-pick is merely a special case that does not\n> deserve a plumbing\".\n\nCorrect. The single cherry-pick will be a trivial fall-out of such a\nsequencer, and the opposite is not true: if we taught `merge-tree` the\noptions `--cherry-pick` or `--revert`, the result would be a dead end\nbecause it does not make sense to extend `merge-tree` to do what the\n`sequencer` would do.\n\n> But that argument leads to \"and the same something-like-sequencer\n> that is all written in C would need '--rebase-merges' that can pick\n> multi-step merge sequences, and single-step merge does not deserve a\n> plumbing\", which is an argument against this topic that is utterly\n> absurd.\n\nBut that `--rebase-merges`-like behavior is far off in the future, and the\nsequencer is not.\n\nIf you step back for a moment and think about the existing use cases where\nwe want to use `merge-tree` on the server side, there are GitHub's Pull\nRequests (and I suspect that all other Git hosting services followed\nsuite), where you have three options:\n\n- merge\n- squash\n- rebase\n\nThe first two options are actually pretty much done, as we already have\na way with the current iteration of `merge-tree` to get the tree, and\nthat's all we need from `merge-tree`, the rest can be done by calling\n`commit-tree` with the appropriate parent(s) and commit messages.\n\nIn contrast, `rebase` will require not only `tree` objects to be\ngenerated, but much more. It is a fundamentally more complex operation\nbecause of that.\n\nNow, if there were already server-side user interfaces to cherry-pick, I\ncould potentially see that the next logical step would be to support\nsomething like the `--force-non-recursive-base=<tree-ish>` option I\nhave already implemented over here (for separate reasons).\n\nBut I am unaware of such a user interface. I _am_ aware of the `rebase`\noption to apply Pull Requests. So I think that's the logical direction\nwe're going from here.\n\n> So why isn't your objection not equally absurd against having a\n> single step cherry-pick or revert primitive as a plumbing?\n\nWell, if you care so deeply about it, I will offer up that patch to\nsupport `--force-non-recursive-base=<tree-ish>` (where the `cherry-pick`\nand `revert` primitive would not need to exist but be the special case of\npassing `CHERRY_PICK_HEAD^` as the appropriate argument).\n\nWhat gets me excited much more, though, is the `rebase` operation. And\ntherefore I would like to spend more focus on that, and focus is a limited\nresource (especially here on the Git mailing list :-)).\n\nCiao,\nDscho\n"},{"id":"449169","messageId":"nycvar.QRO.7.76.6.2202221747470.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BGzWOqgsiRx0jAitbriCCyP8GaVHcCmYV-+CAJZXf1f-w@mail.gmail.com","subject":"Re: [PATCH v3 08/15] merge-ort: allow update messages to be written to different file stream","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-22T16:48:52Z","receivedAt":"2022-02-22T16:49:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Mon, 21 Feb 2022, Elijah Newren wrote:\n\n> On Mon, Feb 21, 2022 at 1:13 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > Hi Elijah,\n> >\n> > On Thu, 3 Feb 2022, Elijah Newren wrote:\n> >\n> > > On Thu, Feb 3, 2022 at 8:24 AM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> > > >\n> > > > On Thu, Feb 03 2022, Elijah Newren wrote:\n> > > >\n> > > > > Man, what a can of worms this all is.  Maybe I really should just drop\n> > > > > patches 5, 6, and 8 for now...\n> > > >\n> > > > Yeah, I really think it's worth it to just sprinkle a tiny bit of\n> > > > if/else (or a macro) here and print to stderr inline or not. We can make\n> > > > some use of some usage.c when there's good reason to do so, but this bit\n> > > > just seems like a needless digression.\n> > > >\n> > > > I hope all of this has helped somewhat ...\n> > >\n> > > Absolutely; thanks for reviewing!  These parts may just end up in me\n> > > dropping some patches for now (since they're not actually being used\n> > > anyway), but I think it's all good feedback.\n> >\n> > So we dropped some useful patches future-proofing `merge-tree` for the\n> > sake of appeasing a refactoring with no immediately obvious benefit? I\n> > really don't like that direction.\n>\n> Even before any of Ævar's comments, I had already noted on my cover\n> letter[1] that \"to be honest, patches 5, 6, & 8 may be less relevant\n> since we're now including these messages on stdout anyway\" -- so I was\n> already wondering if I should defer them to some future series.\n\nAh, that was not clear to me. In that case, I retract my objections.\n\nThanks for clarifying,\nDscho\n"},{"id":"449170","messageId":"nycvar.QRO.7.76.6.2202221751550.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-22T16:54:18Z","receivedAt":"2022-02-22T16:54:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Mon, 21 Feb 2022, Johannes Schindelin wrote:\n\n> [...] since `merge-tree` is a low-level tool meant to be called by\n> programs rather than humans, we need to make sure that those messages\n> remain machine-parseable, even if they contain file names.\n>\n> [...]\n>\n> Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n\nOr maybe much better: use NUL to separate those messages if `-z` is passed\nto `merge-tree`? That would address the issue in one elegant diff.\n\nWhat do you think?\n\nCiao,\nDscho\n"},{"id":"449205","messageId":"CABPp-BFG_05RyVVyiHzOkuoT8=9NftJGp_W+DXd7ktqC5UfvwQ@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-23T02:15:15Z","receivedAt":"2022-02-23T02:15:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 21, 2022 at 2:46 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Fri, 4 Feb 2022, Elijah Newren wrote:\n>\n> > On Fri, Feb 4, 2022 at 3:10 PM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> > >\n> > > On Sat, 29 Jan 2022, Elijah Newren wrote:\n[...]\n> > I've thought about this problem long and hard before (in part because\n> > of some conversations I had with Edward Thompson about libgit2 and\n>\n> Not a big deal for _me_, but I seem to remember that Ed cared a lot about\n> having no p in their surname ;-)\n\nEek!  My apologies to Ed; I'll try to remember and do better.\n\n> > merging at Git Merge 2020).  It wasn't at all clear to me that libgit2\n> > had considered anything beyond simple rename cases.  The only rules I\n> > ever figured out that made sense to me was \"group the stages by target\n> > filename rather than by logical conflict\" (so we get `ls -files -u`\n> > populated) and print a meant-for-human message for each logical\n> > conflict (found in the <Informational Messages> section for\n> > merge-tree), and make NO attempt to connect stages by conflict type.\n> >\n> > I'm sure that's not what you wanted to hear, and maybe doesn't even\n> > play nicely with your design.  But short of ignoring the edge and\n> > corner cases, I don't see how to solve that problem.  If you do just\n> > want to ignore edge and corner cases, then just ignore the\n> > rename/rename case you brought up in the first place and just use\n> > `ls-files -u`-type output as-is within your design.  If you don't want\n> > to ignore edge cases and want something that works with a specific\n> > design that somehow groups conflicted file stages by conflict type,\n> > then we're going to have to dig into all these questions above and do\n> > some big replumbing within merge-ort.\n>\n> There is sometimes a big difference between what I want to hear and what I\n> need to hear. Thank you for providing so many details that I needed to\n> hear.\n>\n> So let's take a step back and look at my goal here, as in: the\n> over-arching goal: to use merge-ort on the server side.\n>\n> From what you said above, it becomes very clear to me that there is very\n> little chance to resolve such conflicts on the server side.\n\nNo, it's still resolvable on the server side.  It's just that attempts\nto break up the information into individual logical conflicts is\nproblematic; if you provide _all_ the informational conflict messages\nto the user, and iterate through the paths with conflicts, then things\nare fine.  It's only when you attempt to find \"the relevant subset\"\nthat things become hard.  Of course, providing it all at once might be\na UI that you hate (perhaps because it's too much like how the command\nline behaves...)\n\n> For example, if a topic branch renames a file differently than the main\n> branch, there is a really good chance that the user tasked with merging\n> the topic branch will have to do a whole lot more than just click a few\n> buttons to perform that task. There might very well be the need to edit\n> files that do not contain merge conflict markers (I like to call those\n> cases \"non-semantic merge conflicts\"), and almost certainly local testing\n> will be necessary.\n\nSidenote: Do you lump in binary merge conflicts with \"non-semantic\nmerge conflicts\"?  You would by your definition, but I'm not sure it\nmatches.\n\nI tend to call things either content-based conflicts or path-based\nconflicts, where content-based usually means textual-based but also\nincludes merges of binaries.\n\n> So I guess the best we can do in those complicated cases is to give a\n> comprehensive overview of the problems in the web UI, with the note that\n> this merge conflict has to be resolved on the local side.\n>\n> Which brings me to the next concern: since `merge-tree` is a low-level\n> tool meant to be called by programs rather than humans, we need to make\n> sure that those messages remain machine-parseable, even if they contain\n> file names.\n>\n> Concretely: while I am not currently aware of any web UI that allows to\n> resolve simple rename/rename conflicts, it is easily conceivable how to\n> implement such a thing. When that happens, we will need to be able to\n> teach the server-side code to discern between the cases that can be\n> handled in the web UI (trivial merge conflicts, trivial rename/rename\n> conflicts) as compared to scenarios where the conflicts are just too\n> complex.\n\nUm, I'm really worried about attempting to make the conflict notices\nmachine parseable.  I don't like that idea at all, and I even tried to\nrule that out already with my wording:\n\"\"\"\nIn all cases, the\n<Informational messages> section has the necessary info, though it is\nnot designed to be machine parseable.\n\"\"\"\nthough maybe I should have been even more explicit.  The restrictions\nthat those messages be stable is too rigid, I think.  I also think\nthey're a poor way to communicate information to a higher level tool.\nI would much rather us add some kind of additional return data\nstructures from merge ort and use them if we want extra info.\n\n> Here's an excerpt from t4301:\n>\n> -- snip --\n> Auto-merging greeting\n> CONFLICT (content): Merge conflict in greeting\n> Auto-merging numbers\n> CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n> CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n> -- snap --\n>\n> This is the complete set of messages provided in the `test conflict\n> notices and such` test case.\n>\n> I immediately notice that every line contains at least one file name.\n> Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\n> does not seem as if the file names are quoted:\n>\n>                 path_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n>\n> (where `path` is used verbatim in a call to `merge_3way()` before that,\n> i.e. it must not have been quoted)\n>\n> I would like to register a wish to ensure that file names with special\n> characters (such as most notably line-feed characters) are quoted in these\n> messages, so that a simple server-side parser can handle messages starting\n> with `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n> \"throw the hands up in the air\" if any other message prefix is seen.\n>\n> Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n> For the `Auto-merging` one, it would be trivial, but I fear that we will\n> have to work a bit on the `path_msg()` function\n> (https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\n> accepts a variable list of arguments without any clue whether the\n> arguments refer to paths or not. (And I would be loathe to switch _all_\n> callers to do the quoting themselves.)\n>\n> I see 28 calls to that function, and at least a couple that pass not only\n> a path but also an OID (e.g.\n> https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n>\n> We could of course be sloppy and pass even OIDs through\n> `sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\n> special characters in them, but it gets more complicated e.g. in\n> https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\n> pass an `strbuf` that contains a somewhat free-form commit message.\n>\n> I guess we could still pass those through `sq_quote_buf_pretty()`, even if\n> they are not paths, to ensure that there are no special characters in the\n> machine-parseable lines.\n>\n> What do you think?\n\nSwitching to single quoting paths as a matter of style might make\nsense, but only if we go through and change every caller to do so so\nthat we can make sure it applies to all paths.  And only paths and not\nOIDs.\n\nBut I'm going to reserve the right in merge-ort to modify, add, or\ndelete any of those messages passed to path_msg(), which might wreak\nhavoc on your attempts to parse those strings.  I think they're a bad\nform for communicating information to a script or program, and trying\nto transform them into such risks making them suboptimal at\ncommunicating info to humans.  These messages should optimize the\nlatter, and if we want something for the former, it should probably be\na new independent bit of info.\n"},{"id":"449208","messageId":"CABPp-BG7id0GfpDee_7ETZ_94BC_i-e_=-u=PrYJeD7d4sVbiw@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202221751550.11118@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-23T03:13:36Z","receivedAt":"2022-02-23T03:13:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Feb 22, 2022 at 8:54 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Mon, 21 Feb 2022, Johannes Schindelin wrote:\n>\n> > [...] since `merge-tree` is a low-level tool meant to be called by\n> > programs rather than humans, we need to make sure that those messages\n> > remain machine-parseable, even if they contain file names.\n> >\n> > [...]\n> >\n> > Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n>\n> Or maybe much better: use NUL to separate those messages if `-z` is passed\n> to `merge-tree`? That would address the issue in one elegant diff.\n>\n> What do you think?\n\nSeparating the combination of messages for a single target path from\nthe combination of messages for a different target path by a NUL\ncharacter may make sense.  Would we also want the messages for a path\nto be prepended by the pathname and a NUL character, in this case, to\nmake it easier to determine which path the group of messages are for?\n\nI'm not sure if that does exactly what you are asking, though.\n\nThe thing that is stored (in opt->priv->output) is a strbuf per path,\nnot an array of strbufs per path.  So, if we have a rename/delete and\na rename/add and a mode conflict for the same \"path\" (A->B on one\nside, other side deletes A and adds a symlink B, resulting in three\nmessages for path \"B\" that are all appended into a single strbuf),\nthen we'll have a single \"message\" which has three newlines.  We can\nadd a NUL character at the end of that, but not between the messages\nwithout restructuring things a bit.\n\nThere's also at least one example, with submodules, where there are\ntwo path_msg() calls for the same individual conflict in order to\nsplit conflict info from resolution advice, and those really shouldn't\nbe thought of as messages for different conflicts.  (I'm starting to\nwonder if the resolution advice should just be tossed; I kept it\nbecause merge-recursive had it, but it might not make sense with\nmerge-ort being used by server side merges.  But even if we toss that\none, I'm not sure I want to commit to one path_msg() call per \"logical\nconflict\".)\n\nBut...maybe this would be good enough for some kind of use you have?\nBecause if you only want to care about \"simple\" cases, you could\npotentially define those as ones with only one newline  in them.\n"},{"id":"449209","messageId":"CABPp-BFc=hcWz1BMW7fAR=Zp3fQ3vxvBtnSYESreYwef_v1K5g@mail.gmail.com","threadId":"57288","inReplyTo":"220221.86y224b80b.gmgdl@evledraar.gmail.com","subject":"Re: machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-23T04:00:14Z","receivedAt":"2022-02-23T04:00:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 21, 2022 at 6:37 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Mon, Feb 21 2022, Johannes Schindelin wrote:\n>\n> [I sent out an empty reply to this earlier by mistake, sorry about that]\n>\n> > [...]\n> > Which brings me to the next concern: since `merge-tree` is a low-level\n> > tool meant to be called by programs rather than humans, we need to make\n> > sure that those messages remain machine-parseable, even if they contain\n> > file names.\n> >\n> > Concretely: while I am not currently aware of any web UI that allows to\n> > resolve simple rename/rename conflicts, it is easily conceivable how to\n> > implement such a thing. When that happens, we will need to be able to\n> > teach the server-side code to discern between the cases that can be\n> > handled in the web UI (trivial merge conflicts, trivial rename/rename\n> > conflicts) as compared to scenarios where the conflicts are just too\n> > complex.\n> >\n> > Here's an excerpt from t4301:\n> >\n> > -- snip --\n> > Auto-merging greeting\n> > CONFLICT (content): Merge conflict in greeting\n> > Auto-merging numbers\n> > CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n> > CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n> > -- snap --\n> >\n> > This is the complete set of messages provided in the `test conflict\n> > notices and such` test case.\n> >\n> > I immediately notice that every line contains at least one file name.\n> > Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\n> > does not seem as if the file names are quoted:\n> >\n> >               path_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n> >\n> > (where `path` is used verbatim in a call to `merge_3way()` before that,\n> > i.e. it must not have been quoted)\n> >\n> > I would like to register a wish to ensure that file names with special\n> > characters (such as most notably line-feed characters) are quoted in these\n> > messages, so that a simple server-side parser can handle messages starting\n> > with `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n> > \"throw the hands up in the air\" if any other message prefix is seen.\n> >\n> > Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n> > For the `Auto-merging` one, it would be trivial, but I fear that we will\n> > have to work a bit on the `path_msg()` function\n> > (https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\n> > accepts a variable list of arguments without any clue whether the\n> > arguments refer to paths or not. (And I would be loathe to switch _all_\n> > callers to do the quoting themselves.)\n> >\n> > I see 28 calls to that function, and at least a couple that pass not only\n> > a path but also an OID (e.g.\n> > https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n> >\n> > We could of course be sloppy and pass even OIDs through\n> > `sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\n> > special characters in them, but it gets more complicated e.g. in\n> > https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\n> > pass an `strbuf` that contains a somewhat free-form commit message.\n> >\n> > I guess we could still pass those through `sq_quote_buf_pretty()`, even if\n> > they are not paths, to ensure that there are no special characters in the\n> > machine-parseable lines.\n> >\n> > What do you think?\n>\n> That sounds like a rather nasty hack, this is too, but demonstrates that\n> we can pretty easily extract this in a machine-readable format with just\n> a few lines now):\n>\n> diff --git a/merge-ort.c b/merge-ort.c\n> index 8a5f201d190..a906881f9b3 100644\n> --- a/merge-ort.c\n> +++ b/merge-ort.c\n> @@ -633,7 +633,7 @@ static void path_msg(struct merge_options *opt,\n>                      int omittable_hint, /* skippable under --remerge-diff */\n>                      const char *fmt, ...)\n>  {\n> -       va_list ap;\n> +       va_list ap, cp;\n>         struct strbuf *sb, *dest;\n>         struct strbuf tmp = STRBUF_INIT;\n>\n> @@ -650,7 +650,9 @@ static void path_msg(struct merge_options *opt,\n>\n>         dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);\n>\n> +       va_copy(cp, ap);\n>         va_start(ap, fmt);\n> +\n>         if (opt->priv->call_depth) {\n>                 strbuf_addchars(dest, ' ', 2);\n>                 strbuf_addstr(dest, \"From inner merge:\");\n> @@ -659,6 +661,15 @@ static void path_msg(struct merge_options *opt,\n>         strbuf_vaddf(dest, fmt, ap);\n>         va_end(ap);\n>\n> +       va_start(cp, fmt);\n> +       trace2_region_enter_printf(\"merge\", \"conflict/path\", opt->repo, \"%s\", path);\n> +       trace2_region_leave(\"merge\", \"conflict/path\", opt->repo);\n> +       trace2_region_enter_printf(\"merge\", \"conflict/fmt\", opt->repo, \"%s\", fmt);\n> +       trace2_region_leave(\"merge\", \"conflict/fmt\", opt->repo);\n> +       trace2_region_enter_printf_va(\"merge\", \"conflict/msg\", opt->repo, fmt, cp);\n> +       trace2_region_leave(\"merge\", \"conflict/msg\", opt->repo);\n> +       va_end(cp);\n> +\n>         if (opt->record_conflict_msgs_as_headers) {\n>                 int i_sb = 0, i_tmp = 0;\n>\n> You can run that with one of the tests added in this series to get the\n> output as JSON, e.g.:\n>\n>      GIT_TRACE2_EVENT=/dev/stderr GIT_TRACE2_EVENT_NESTING=10 ~/g/git/git merge-tree --write-tree --no-messages --name-only --messages side1 side2 2>&1|jq -r .| grep '\"msg\"'\n>       \"msg\": \"whatever~side1\"\n>       \"msg\": \"CONFLICT (file/directory): directory in the way of %s from %s; moving it to %s instead.\"\n>       \"msg\": \"CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\"\n>       \"msg\": \"whatever~side1\"\n>       \"msg\": \"CONFLICT (modify/delete): %s deleted in %s and modified in %s.  Version %s of %s left in tree.\"\n>       \"msg\": \"CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\"\n>       \"msg\": \"numbers\"\n>       \"msg\": \"Auto-merging %s\"\n>       \"msg\": \"Auto-merging numbers\"\n>       \"msg\": \"greeting\"\n>       \"msg\": \"Auto-merging %s\"\n>       \"msg\": \"Auto-merging greeting\"\n>       \"msg\": \"greeting\"\n>       \"msg\": \"CONFLICT (%s): Merge conflict in %s\"\n>       \"msg\": \"CONFLICT (content): Merge conflict in greeting\"\n>\n> A \"proper\" fix for this doesn't sound too hard, we'd just instrument the\n> path_msg() function to pass along some \"message category\", see\n> e.g. unpack_plumbing_errors in unpack-trees.c for one example of such a\n> thing, or the \"enum fsck_msg_id\".\n>\n> Then we'd just allow you to emit any of the sprintf() format itself, or\n> the expanded version, the path, or an id like \"CONFLICT:file/directory\"\n> or \"auto-merging\" etc.\n\nI don't see how this helps solve the problem Dscho was bringing up at\nall.  Your reference to \"the path\" means you've missed his whole\ncomplaint -- that with more complex conflicts (renames, directory/file\nconflicts resolved via moving the file out of the way, mode conflicts\nresolved by moving both files out of the way, etc) there are multiple\npaths involved and he's trying to determine what those paths are.\nHe's particularly focusing on rename/rename cases where a single path\nwas renamed differently by the two sides of history (which results in\na conflict message only being associated with the path from the merge\nbase in order to avoid repeating the same message 2-3 times, but that\none message has three distinct paths embedded in the string).\n\nAlso, the additional paths is not part of the API to path_msg; it's\nmerely embedded in a string.  (And, in case it bears repeating: as\nmentioned elsewhere, we cannot assume there will only be one\npath_msg() call per path, and we at least currently can't assume that\neach path_msg() call is for a separate logical conflict; there might\nbe two for a single \"conflict\".)\n\nI agree that parsing these meant-for-human-consumption (and not\npromised to be stable) messages is not a good way to go, but\npretending the current API has enough info to answer his questions\nisn't right either.\n\n> I think that would be particularly useful in conjuction with the\n> --format changes I was proposing for this (and hacked up a WIP patch\n> for). You could just have a similar --format-messages or whatever.\n>\n> Then you could pick \\0\\0 as a delimiter for your \"main\" --format, and\n> \"\\0\" as the delimiter for your --format-messages, and thus be able to\n> parse N-nested \\0-delimited content.\n\nTo be honest, the --format stuff is sounding a little bit like a\nsolution in search of a problem.\n"},{"id":"449214","messageId":"4a7cd5542bb2f89b4874e4115542ccee9c4639af.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 01/12] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:42Z","receivedAt":"2022-02-23T07:47:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"449215","messageId":"4780ff6784d426bf0a96859ef9bf9c14e87d5f50.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 02/12] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:43Z","receivedAt":"2022-02-23T07:47:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"449216","messageId":"60253745f5c59ac4c75d46df1f98ee722523f166.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 03/12] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:44Z","receivedAt":"2022-02-23T07:47:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 84 +++++++++++++++++++++++++++++++++++++++-----\n git.c                |  2 +-\n 2 files changed, 76 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..0f9d928e862 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,74 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+enum mode {\n+\tMODE_UNKNOWN,\n+\tMODE_TRIVIAL,\n+\tMODE_REAL,\n+};\n+\n+struct merge_tree_options {\n+\tint mode;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n+\t\t\t    MODE_REAL),\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n+\t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tswitch (o.mode) {\n+\tdefault:\n+\t\tBUG(\"unexpected command mode %d\", o.mode);\n+\tcase MODE_UNKNOWN:\n+\t\tswitch (argc) {\n+\t\tdefault:\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\tcase 2:\n+\t\t\to.mode = MODE_REAL;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\to.mode = MODE_TRIVIAL;\n+\t\t\tbreak;\n+\t\t}\n+\t\texpected_remaining_argc = argc;\n+\t\tbreak;\n+\tcase MODE_REAL:\n+\t\texpected_remaining_argc = 2;\n+\t\tbreak;\n+\tcase MODE_TRIVIAL:\n+\t\texpected_remaining_argc = 3;\n+\t\tbreak;\n+\t}\n+\n+\tif (argc != expected_remaining_argc)\n+\t\tusage_with_options(merge_tree_usage, mt_options);\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.mode == MODE_REAL)\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff21be21f3..6090a1289db 100644\n--- a/git.c\n+++ b/git.c\n@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"449217","messageId":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v5.git.1645340082.gitgitgadget@gmail.com","subject":"[PATCH v6 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:41Z","receivedAt":"2022-02-23T07:47:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs. It could\nbe used to do merges when in a bare repository (thus potentially making it\nof interest to Git hosting sites, i.e. \"Server side merges\"), or for doing a\nmerge of branches that aren't checked out.\n\nIt does not handle similar functionality for cherry-picks, rebases, or\nreverts; that is also of interest, but is being deferred for a future\nseries.\n\n== Quick Overview ==\n\n * Patches 1-2: preparatory cleanups\n * Patches 3-4: implement basic real merges\n * Patches 5-6: include informational messages (\"CONFLICT\" messages and\n   such) in output\n * Patches 7-10: add ability to include ls-files -u style of info in the\n   output\n * Patch 11: support --allow-unrelated-histories\n * Patch 12: augment the manual with potential usage mistakes\n\n== Updates Log ==\n\nStuff NOT included that reviewers brought up in various rounds (and which\nmight still be an open question):\n\n * Having -z affect the informational messages section[1]\n * Having a machine-parseable variant of information from the informational\n   messages section[2]\n * Very generic (mode, oid, stage, filename) printing formatting[3]\n * Providing similar functionality for doing cherry-picks/rebases/reverts,\n   i.e. a scheme for three-way merges with a specified merge-base[4]. That's\n   being deferred to a future series. [1]\n   https://lore.kernel.org/git/CABPp-BG7id0GfpDee_7ETZ_94BC_i-e_=-u=PrYJeD7d4sVbiw@mail.gmail.com/\n   [2]\n   https://lore.kernel.org/git/nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet/,\n   https://lore.kernel.org/git/220221.86y224b80b.gmgdl@evledraar.gmail.com/\n   [3]\n   https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n   [4]\n   https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n\nUpdates since v5:\n\n * Used reverse_commit_list() to reverse a commit_list without extra\n   allocations\n * Several documentation updates\n\nUpdates since v4:\n\n * Fixed double \"is\" in documentation.\n * Fixed a few small items with testcases\n\nUpdates since v3:\n\n * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n   being used and opened a can of worms[1]\n * [Patch 3] Restructured argument checking, including using an enum\n * [Patch 4] Restored the extended paragraph about the deprecated form of\n   git-merge-tree, mentioned write-tree in plumbing commands, and a few\n   other small fixups to the documentation\n * [Patch 4] Also provide an example of a clean merge rather than just a\n   conflicted one\n * [Patch 6] Fix the incompatible arguments check and add some tests for it\n * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n   easier to read (less repeated sed)\n * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n   option for now\n * [Patch 10] When -z passed, the tree in the first section should have a\n   trailing NUL rather than trailing newline [1]\n   https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n\nUpdates since v2:\n\n * Improved patches from Dscho for the diff_warn_rename_limit() handling\n * Add a -z option for NUL-terminated conflict info lines (so that filenames\n   do not have to be quoted)\n\nUpdates since v1 (or v3 depending on how you count; thanks to René, Ævar,\nChristian, Dscho for very helpful feedback):\n\n * New patch from Dscho allowing diff_warn_rename_limit() to print somewhere\n   other than stdout (I hope he's okay with me including his Signed-off-by)\n * Now prints filenames relative to prefix, much like ls-files\n * Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave\n   it a -l shorthand; I'm wondering if I should just drop this option,\n   though.\n * And numerous cleanups, in lots of areas:\n   * Multiple parse-options cleanups\n   * Lots of commit message cleanups\n   * Wording tweaks to the \"Description\" section of the manual\n   * Several small code cleanups\n * I dropped the RFC label\n\n[There were also two submissions of a previous series; see\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/]\n[https://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/%5D]\n\nUpdates since original submission v2 (thanks to Christian, Dscho, Ramsay,\nand René for suggestions and comments):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since original submission v1 (thanks to Johannes Altmanninger and\nFabian for suggestions):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\nElijah Newren (12):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-tree: allow `ls-files -u` style info to be NUL terminated\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\n Documentation/git-merge-tree.txt | 228 +++++++++++++++++++++++++++--\n builtin/merge-tree.c             | 186 ++++++++++++++++++++++--\n git.c                            |   2 +-\n merge-ort.c                      | 109 +++++++++-----\n merge-ort.h                      |  29 ++++\n t/t4301-merge-tree-write-tree.sh | 238 +++++++++++++++++++++++++++++++\n 6 files changed, 730 insertions(+), 62 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\n\nbase-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n\nRange-diff vs v5:\n\n  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()\n  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function\n  3:  60253745f5c =  3:  60253745f5c merge-tree: add option parsing and initial shell for real merge function\n  4:  7994775a934 !  4:  f8266d39c1b merge-tree: implement real merges\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +'git merge-tree' [--write-tree] <branch1> <branch2>\n      +'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n       \n     ++[[NEWMERGE]]\n       DESCRIPTION\n       -----------\n      -Reads three tree-ish, and output trivial merge results and\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      -index.  For this reason, the output from the command omits\n      -entries that match the <branch1> tree.\n      +\n     ++This command has a modern `--write-tree` mode and a deprecated\n     ++`--trivial-merge` mode.  With the exception of the\n     ++<<DEPMERGE,DEPRECATED DESCRIPTION>> section at the end, the rest of\n     ++this documentation describes modern `--write-tree` mode.\n     ++\n      +Performs a merge, but does not make any new commits and does not read\n      +from or write to either the working tree or index.\n      +\n     -+The first form will merge the two branches, doing a real merge.  A real\n     -+merge is distinguished from a trivial merge in that it includes:\n     ++The performed merge will use the same feature as the \"real\"\n     ++linkgit:git-merge[1], including:\n      +\n      +  * three way content merges of individual files\n      +  * rename detection\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +    merge base, creating a virtual merge base by merging the merge bases)\n      +  * etc.\n      +\n     -+After the merge completes, the first form will create a new toplevel\n     -+tree object.  See `OUTPUT` below for details.\n     -+\n     -+The second form is deprecated; it is kept for backward compatibility\n     -+reasons but may be deleted in the future.  Other than the optional\n     -+`--trivial-merge`, it accepts no options.  It can only do a trivial\n     -+merge.  It reads three tree-ish, and outputs trivial merge results and\n     -+conflicting stages to the standard output in a semi-diff format.\n     -+Since this was designed for higher level scripts to consume and merge\n     -+the results back into the index, it omits entries that match\n     -+<branch1>.  The result of this second form is similar to what\n     -+three-way 'git read-tree -m' does, but instead of storing the results\n     -+in the index, the command outputs the entries to the standard output.\n     -+This form not only has limited applicability, the output format is\n     -+also difficult to work with, and it will generally be less performant\n     -+than the first form even on successful merges (especially if working\n     -+in large repositories).  The remainder of this manual will only\n     -+discuss the first form.\n     ++After the merge completes, a new toplevel tree object is created.  See\n     ++`OUTPUT` below for details.\n      +\n     ++[[OUTPUT]]\n      +OUTPUT\n      +------\n      +\n     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)\n      +USAGE NOTES\n      +-----------\n      +\n     -+git-merge-tree was written to be low-level plumbing, similar to\n     -+hash-object, mktree, commit-tree, write-tree, update-ref, and mktag.\n     -+Thus, it could be used as a part of a series of steps such as\n     ++This command is intended as low-level plumbing, similar to\n     ++linkgit:git-hash-object[1], linkgit:git-mktree[1],\n     ++linkgit:git-commit-tree[1], linkgit:git-write-tree[1],\n     ++linkgit:git-update-ref[1], and linkgit:git-mktag[1].  Thus, it can be\n     ++used as a part of a series of steps such as:\n      +\n      +       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n      +       test $? -eq 0 || die \"There were conflicts...\"\n      +       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n      +       git update-ref $BRANCH1 $NEWCOMMIT\n     ++\n     ++[[DEPMERGE]]\n     ++DEPRECATED DESCRIPTION\n     ++----------------------\n     ++\n     ++Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this\n     ++documentation, this section describes the deprecated `--trivial-merge`\n     ++mode.\n     ++\n     ++Other than the optional `--trivial-merge`, this mode accepts no\n     ++options.\n     ++\n     ++This mode reads three tree-ish, and outputs trivial merge results and\n     ++conflicting stages to the standard output in a semi-diff format.\n     ++Since this was designed for higher level scripts to consume and merge\n     ++the results back into the index, it omits entries that match\n     ++<branch1>.  The result of this second form is similar to what\n     ++three-way 'git read-tree -m' does, but instead of storing the results\n     ++in the index, the command outputs the entries to the standard output.\n     ++\n     ++This form not only has limited applicability (a trivial merge cannot\n     ++handle content merges of individual files, rename detection, proper\n     ++directory/file conflict handling, etc.), the output format is also\n     ++difficult to work with, and it will generally be less performant than\n     ++the first form even on successful merges (especially if working in\n     ++large repositories).\n       \n       GIT\n       ---\n     @@ builtin/merge-tree.c: struct merge_tree_options {\n       {\n      -\tdie(_(\"real merges are not yet implemented\"));\n      +\tstruct commit *parent1, *parent2;\n     -+\tstruct commit_list *common;\n      +\tstruct commit_list *merge_bases = NULL;\n     -+\tstruct commit_list *j;\n      +\tstruct merge_options opt;\n      +\tstruct merge_result result = { 0 };\n      +\n     @@ builtin/merge-tree.c: struct merge_tree_options {\n      +\t * Get the merge bases, in reverse order; see comment above\n      +\t * merge_incore_recursive in merge-ort.h\n      +\t */\n     -+\tcommon = get_merge_bases(parent1, parent2);\n     -+\tif (!common)\n     ++\tmerge_bases = get_merge_bases(parent1, parent2);\n     ++\tif (!merge_bases)\n      +\t\tdie(_(\"refusing to merge unrelated histories\"));\n     -+\tfor (j = common; j; j = j->next)\n     -+\t\tcommit_list_insert(j->item, &merge_bases);\n     ++\tmerge_bases = reverse_commit_list(merge_bases);\n      +\n      +\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n      +\tif (result.clean < 0)\n  5:  e0f95e094cf =  5:  6629af14919 merge-ort: split out a separate display_update_messages() function\n  6:  90c4adecb23 !  6:  17b57efb714 merge-tree: support including merge messages in output\n     @@ Documentation/git-merge-tree.txt: git-merge-tree - Perform merge without touchin\n      +'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n       'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n       \n     - DESCRIPTION\n     -@@ Documentation/git-merge-tree.txt: than the first form even on successful merges (especially if working\n     - in large repositories).  The remainder of this manual will only\n     - discuss the first form.\n     + [[NEWMERGE]]\n     +@@ Documentation/git-merge-tree.txt: linkgit:git-merge[1], including:\n     + After the merge completes, a new toplevel tree object is created.  See\n     + `OUTPUT` below for details.\n       \n      +OPTIONS\n      +-------\n     @@ Documentation/git-merge-tree.txt: than the first form even on successful merges\n      +\tdefault is to include these messages if there are merge\n      +\tconflicts, and to omit them otherwise.\n      +\n     + [[OUTPUT]]\n       OUTPUT\n       ------\n       \n      -For either a successful or conflicted merge, the output from\n      -git-merge-tree is simply one line:\n     -+By default, for a successful merge, the output from git-merge-tree is\n     -+simply one line:\n     ++For a successful merge, the output from git-merge-tree is simply one\n     ++line:\n      +\n      +\t<OID of toplevel tree>\n      +\n     @@ Documentation/git-merge-tree.txt: than the first form even on successful merges\n      -The printed tree object corresponds to what would be checked out in\n      -the working tree at the end of `git merge`, and thus may have files\n      -with conflict markers in them.\n     ++[[OIDTLT]]\n      +OID of toplevel tree\n      +~~~~~~~~~~~~~~~~~~~~\n      +\n     @@ Documentation/git-merge-tree.txt: than the first form even on successful merges\n      +working tree at the end of `git merge`.  If there were conflicts, then\n      +files within this tree may have embedded conflict markers.\n      +\n     ++[[IM]]\n      +Informational messages\n      +~~~~~~~~~~~~~~~~~~~~~~\n      +\n     @@ Documentation/git-merge-tree.txt: than the first form even on successful merges\n       \n       EXIT STATUS\n       -----------\n     -@@ Documentation/git-merge-tree.txt: Thus, it could be used as a part of a series of steps such as\n     +@@ Documentation/git-merge-tree.txt: used as a part of a series of steps such as:\n              NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n              git update-ref $BRANCH1 $NEWCOMMIT\n       \n     -+Note that when the exit status is non-zero, NEWTREE in this sequence\n     ++Note that when the exit status is non-zero, `NEWTREE` in this sequence\n      +will contain a lot more output than just a tree.\n      +\n     - GIT\n     - ---\n     - Part of the linkgit:git[1] suite\n     + [[DEPMERGE]]\n     + DEPRECATED DESCRIPTION\n     + ----------------------\n      \n       ## builtin/merge-tree.c ##\n      @@ builtin/merge-tree.c: enum mode {\n  7:  12e2351092a =  7:  4c8f42372dd merge-ort: provide a merge_get_conflicted_files() helper function\n  8:  5bb7d3725ad !  8:  7b1ee417f3d merge-tree: provide a list of which files have conflicts\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: simply one line:\n     +@@ Documentation/git-merge-tree.txt: line:\n       Whereas for a conflicted merge, the output is by default of the form:\n       \n       \t<OID of toplevel tree>\n     @@ Documentation/git-merge-tree.txt: This is a tree object that represents what wou\n       working tree at the end of `git merge`.  If there were conflicts, then\n       files within this tree may have embedded conflict markers.\n       \n     ++[[CFI]]\n      +Conflicted file list\n      +~~~~~~~~~~~~~~~~~~~~\n      +\n     @@ Documentation/git-merge-tree.txt: This is a tree object that represents what wou\n      +as explained for the configuration variable `core.quotePath` (see\n      +linkgit:git-config[1]).\n      +\n     + [[IM]]\n       Informational messages\n       ~~~~~~~~~~~~~~~~~~~~~~\n     - \n      \n       ## builtin/merge-tree.c ##\n      @@\n     @@ builtin/merge-tree.c: struct merge_tree_options {\n      +\t\t      const char *prefix)\n       {\n       \tstruct commit *parent1, *parent2;\n     - \tstruct commit_list *common;\n     + \tstruct commit_list *merge_bases = NULL;\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t\to->show_messages = !result.clean;\n       \n  9:  3c2ca198cec !  9:  f1231a8fbc8 merge-tree: provide easy access to `ls-files -u` style info\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: discuss the first form.\n     +@@ Documentation/git-merge-tree.txt: After the merge completes, a new toplevel tree object is created.  See\n       OPTIONS\n       -------\n       \n     @@ Documentation/git-merge-tree.txt: discuss the first form.\n       --[no-]messages::\n       \tWrite any informational messages such as \"Auto-merging <path>\"\n       \tor CONFLICT notices to the end of stdout.  If unspecified, the\n     -@@ Documentation/git-merge-tree.txt: simply one line:\n     +@@ Documentation/git-merge-tree.txt: line:\n       Whereas for a conflicted merge, the output is by default of the form:\n       \n       \t<OID of toplevel tree>\n     @@ Documentation/git-merge-tree.txt: simply one line:\n       \t<Informational messages>\n       \n       These are discussed individually below.\n     -@@ Documentation/git-merge-tree.txt: This is a tree object that represents what would be checked out in the\n     - working tree at the end of `git merge`.  If there were conflicts, then\n     +@@ Documentation/git-merge-tree.txt: working tree at the end of `git merge`.  If there were conflicts, then\n       files within this tree may have embedded conflict markers.\n       \n     + [[CFI]]\n      -Conflicted file list\n      +Conflicted file info\n       ~~~~~~~~~~~~~~~~~~~~\n     @@ Documentation/git-merge-tree.txt: This is a tree object that represents what wou\n      +the `--name-only` option is passed, the mode, object, and stage will\n      +be omitted.\n       \n     + [[IM]]\n       Informational messages\n       ~~~~~~~~~~~~~~~~~~~~~~\n       \n     @@ Documentation/git-merge-tree.txt: This is a tree object that represents what wou\n       \n         * \"Auto-merging <file>\"\n         * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n     -@@ Documentation/git-merge-tree.txt: Thus, it could be used as a part of a series of steps such as\n     - Note that when the exit status is non-zero, NEWTREE in this sequence\n     +@@ Documentation/git-merge-tree.txt: used as a part of a series of steps such as:\n     + Note that when the exit status is non-zero, `NEWTREE` in this sequence\n       will contain a lot more output than just a tree.\n       \n     -+git-merge-tree was written to provide users with the same information\n     -+that they'd have access to if using `git merge`:\n     -+  * what would be written to the working tree (the <OID of toplevel tree>)\n     ++For conflicts, the output includes the same information that you'd get\n     ++with linkgit:git-merge[1]:\n     ++\n     ++  * what would be written to the working tree (the\n     ++    <<OIDTLT,OID of toplevel tree>>)\n      +  * the higher order stages that would be written to the index (the\n     -+    <Conflicted file info>)\n     -+  * any messages that would have been printed to stdout (the <Informational\n     -+    messages>)\n     ++    <<CFI,Conflicted file info>>)\n     ++  * any messages that would have been printed to stdout (the\n     ++    <<IM,Informational messages>>)\n      +\n     - GIT\n     - ---\n     - Part of the linkgit:git[1] suite\n     + [[DEPMERGE]]\n     + DEPRECATED DESCRIPTION\n     + ----------------------\n      \n       ## builtin/merge-tree.c ##\n      @@ builtin/merge-tree.c: enum mode {\n 10:  6e89e17693a ! 10:  22297e6ce75 merge-tree: allow `ls-files -u` style info to be NUL terminated\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: discuss the first form.\n     +@@ Documentation/git-merge-tree.txt: After the merge completes, a new toplevel tree object is created.  See\n       OPTIONS\n       -------\n       \n     @@ Documentation/git-merge-tree.txt: discuss the first form.\n      +\tDo not quote filenames in the <Conflicted file info> section,\n      +\tand end each filename with a NUL character rather than\n      +\tnewline.  Also begin the messages section with a NUL character\n     -+\tinstead of a newline.  See OUTPUT below for more information.\n     ++\tinstead of a newline.  See <<OUTPUT>> below for more information.\n      +\n       --name-only::\n       \tIn the Conflicted file info section, instead of writing a list\n     @@ Documentation/git-merge-tree.txt: OID of toplevel tree\n      +files within this tree may have embedded conflict markers.  This section\n      +is always followed by a newline (or NUL if `-z` is passed).\n       \n     + [[CFI]]\n       Conflicted file info\n     - ~~~~~~~~~~~~~~~~~~~~\n      @@ Documentation/git-merge-tree.txt: This is a sequence of lines with the format\n       The filename will be quoted as explained for the configuration\n       variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n     @@ Documentation/git-merge-tree.txt: This is a sequence of lines with the format\n      +be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n      +character instead of a newline character.\n       \n     + [[IM]]\n       Informational messages\n       ~~~~~~~~~~~~~~~~~~~~~~\n       \n 11:  6ddd5ffde9c ! 11:  db73c6dd823 merge-tree: add a --allow-unrelated-histories flag\n     @@ Documentation/git-merge-tree.txt: OPTIONS\n      +\tshare no common history.  This flag can be given to override that\n      +\tcheck and make the merge proceed anyway.\n      +\n     + [[OUTPUT]]\n       OUTPUT\n       ------\n     - \n      \n       ## builtin/merge-tree.c ##\n      @@ builtin/merge-tree.c: enum mode {\n     @@ builtin/merge-tree.c: enum mode {\n      @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \t * merge_incore_recursive in merge-ort.h\n       \t */\n     - \tcommon = get_merge_bases(parent1, parent2);\n     --\tif (!common)\n     -+\tif (!common && !o->allow_unrelated_histories)\n     + \tmerge_bases = get_merge_bases(parent1, parent2);\n     +-\tif (!merge_bases)\n     ++\tif (!merge_bases && !o->allow_unrelated_histories)\n       \t\tdie(_(\"refusing to merge unrelated histories\"));\n     - \tfor (j = common; j; j = j->next)\n     - \t\tcommit_list_insert(j->item, &merge_bases);\n     + \tmerge_bases = reverse_commit_list(merge_bases);\n     + \n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n       \t\t\t   &o.name_only,\n       \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n 12:  7abf633b638 ! 12:  d58a7c7a9f6 git-merge-tree.txt: add a section on potentional usage mistakes\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Documentation/git-merge-tree.txt ##\n     -@@ Documentation/git-merge-tree.txt: that they'd have access to if using `git merge`:\n     -   * any messages that would have been printed to stdout (the <Informational\n     -     messages>)\n     +@@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:\n     +   * any messages that would have been printed to stdout (the\n     +     <<IM,Informational messages>>)\n       \n      +MISTAKES TO AVOID\n      +-----------------\n      +\n      +Do NOT look through the resulting toplevel tree to try to find which\n     -+files conflict; parse the <Conflicted file info> section instead.  Not\n     -+only would parsing an entire tree be horrendously slow in large\n     ++files conflict; parse the <<CFI,Conflicted file info>> section instead.\n     ++Not only would parsing an entire tree be horrendously slow in large\n      +repositories, there are numerous types of conflicts not representable by\n      +conflict markers (modify/delete, mode conflict, binary file changed on\n      +both sides, file/directory conflicts, various rename conflict\n      +permutations, etc.)\n      +\n     -+Do NOT interpret an empty <Conflicted file info> list as a clean merge;\n     -+check the exit status.  A merge can have conflicts without having\n     ++Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean\n     ++merge; check the exit status.  A merge can have conflicts without having\n      +individual files conflict (there are a few types of directory rename\n      +conflicts that fall into this category, and others might also be added\n      +in the future).\n      +\n      +Do NOT attempt to guess or make the user guess the conflict types from\n     -+the <Conflicted file info> list.  The information there is insufficient\n     -+to do so.  For example: Rename/rename(1to2) conflicts (both sides\n     -+renamed the same file differently) will result in three different file\n     -+having higher order stages (but each only has one higher order stage),\n     -+with no way (short of the <Informational messages> section) to determine\n     -+which three files are related.  File/directory conflicts also result in\n     -+a file with exactly one higher order stage.\n     ++the <<CFI,Conflicted file info>> list.  The information there is\n     ++insufficient to do so.  For example: Rename/rename(1to2) conflicts (both\n     ++sides renamed the same file differently) will result in three different\n     ++file having higher order stages (but each only has one higher order\n     ++stage), with no way (short of the <<IM,Informational messages>> section)\n     ++to determine which three files are related.  File/directory conflicts\n     ++also result in a file with exactly one higher order stage.\n      +Possibly-involved-in-directory-rename conflicts (when\n      +\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n      +a file with exactly one higher order stage.  In all cases, the\n     -+<Informational messages> section has the necessary info, though it is\n     -+not designed to be machine parseable.\n     ++<<IM,Informational messages>> section has the necessary info, though it\n     ++is not designed to be machine parseable.\n      +\n     -+Do NOT assume all filenames listed in the <Informational messages>\n     ++Do NOT assume all filenames listed in the <<IM,Informational messages>>\n      +section had conflicts.  Messages can be included for files that have no\n      +conflicts, such as \"Auto-merging <file>\".\n      +\n     -+AVOID taking the OIDS from the <Conflicted file info> and re-merging\n     -+them to present the conflicts to the user.  This will lose information.\n     -+Instead, look up the version of the file found within the <OID of\n     -+toplevel tree> and show that instead.  In particular, the latter will\n     -+have conflict markers annotated with the original branch/commit being\n     -+merged and, if renames were involved, the original filename.  While you\n     -+could include the original branch/commit in the conflict marker\n     -+annotations when re-merging, the original filename is not available from\n     -+the <Conflicted file info> and thus you would be losing information that\n     -+might help the user resolve the conflict.\n     ++AVOID taking the OIDS from the <<CFI,Conflicted file info>> and\n     ++re-merging them to present the conflicts to the user.  This will lose\n     ++information.  Instead, look up the version of the file found within the\n     ++<<OIDTLT,OID of toplevel tree>> and show that instead.  In particular,\n     ++the latter will have conflict markers annotated with the original\n     ++branch/commit being merged and, if renames were involved, the original\n     ++filename.  While you could include the original branch/commit in the\n     ++conflict marker annotations when re-merging, the original filename is\n     ++not available from the <<CFI,Conflicted file info>> and thus you would\n     ++be losing information that might help the user resolve the conflict.\n      +\n     - GIT\n     - ---\n     - Part of the linkgit:git[1] suite\n     + [[DEPMERGE]]\n     + DEPRECATED DESCRIPTION\n     + ----------------------\n\n-- \ngitgitgadget\n"},{"id":"449218","messageId":"f8266d39c1b3248c06f8e1b13e0126e7ca1df6d1.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 04/12] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:45Z","receivedAt":"2022-02-23T07:47:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean), 1 (conflicts present), anything else\n    (merge could not be performed; unknown if clean or conflicted)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability (meaning that NEWTREE would be a lot more than a tree in the\ncase of conflicts).\n\nThis also marks the traditional trivial merge of merge-tree as\ndeprecated.  The trivial merge not only had limited applicability, the\noutput format was also difficult to work with (and its format\nundocumented), and will generally be less performant than real merges.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  98 ++++++++++++++++++++++++----\n builtin/merge-tree.c             |  41 +++++++++++-\n t/t4301-merge-tree-write-tree.sh | 106 +++++++++++++++++++++++++++++++\n 3 files changed, 232 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..2a9c91328de 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,100 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n+[[NEWMERGE]]\n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+This command has a modern `--write-tree` mode and a deprecated\n+`--trivial-merge` mode.  With the exception of the\n+<<DEPMERGE,DEPRECATED DESCRIPTION>> section at the end, the rest of\n+this documentation describes modern `--write-tree` mode.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The performed merge will use the same feature as the \"real\"\n+linkgit:git-merge[1], including:\n+\n+  * three way content merges of individual files\n+  * rename detection\n+  * proper directory/file conflict handling\n+  * recursive ancestor consolidation (i.e. when there is more than one\n+    merge base, creating a virtual merge base by merging the merge bases)\n+  * etc.\n+\n+After the merge completes, a new toplevel tree object is created.  See\n+`OUTPUT` below for details.\n+\n+[[OUTPUT]]\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1 (and the output is unspecified).\n+\n+USAGE NOTES\n+-----------\n+\n+This command is intended as low-level plumbing, similar to\n+linkgit:git-hash-object[1], linkgit:git-mktree[1],\n+linkgit:git-commit-tree[1], linkgit:git-write-tree[1],\n+linkgit:git-update-ref[1], and linkgit:git-mktag[1].  Thus, it can be\n+used as a part of a series of steps such as:\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n+\n+[[DEPMERGE]]\n+DEPRECATED DESCRIPTION\n+----------------------\n+\n+Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this\n+documentation, this section describes the deprecated `--trivial-merge`\n+mode.\n+\n+Other than the optional `--trivial-merge`, this mode accepts no\n+options.\n+\n+This mode reads three tree-ish, and outputs trivial merge results and\n+conflicting stages to the standard output in a semi-diff format.\n+Since this was designed for higher level scripts to consume and merge\n+the results back into the index, it omits entries that match\n+<branch1>.  The result of this second form is similar to what\n+three-way 'git read-tree -m' does, but instead of storing the results\n+in the index, the command outputs the entries to the standard output.\n+\n+This form not only has limited applicability (a trivial merge cannot\n+handle content merges of individual files, rename detection, proper\n+directory/file conflict handling, etc.), the output format is also\n+difficult to work with, and it will generally be less performant than\n+the first form even on successful merges (especially if working in\n+large repositories).\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 0f9d928e862..2332525d8bd 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -398,7 +401,43 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = branch1;\n+\topt.branch2 = branch2;\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tmerge_bases = get_merge_bases(parent1, parent2);\n+\tif (!merge_bases)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tmerge_bases = reverse_commit_list(merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\tputs(oid_to_hex(&result.tree->object.oid));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nnew file mode 100755\nindex 00000000000..6d321652e21\n--- /dev/null\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+if test \"$GIT_TEST_MERGE_ALGORITHM\" != \"ort\"\n+then\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+fi\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\tgit branch side3 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications &&\n+\n+\tgit checkout side3 &&\n+\tgit mv numbers sequence &&\n+\ttest_tick &&\n+\tgit commit -m rename-numbers\n+'\n+\n+test_expect_success 'Clean merge' '\n+\tTREE_OID=$(git merge-tree --write-tree side1 side3) &&\n+\tq_to_tab <<-EOF >expect &&\n+\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n+\t100644 blob $(git rev-parse side1:numbers)Qsequence\n+\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n+\tEOF\n+\n+\tgit ls-tree $TREE_OID >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(git rev-parse AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tsed -e s/HEAD/side1/ tmp >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"449219","messageId":"6629af14919a5133c656a0592a18e2adb8925dc4.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 05/12] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:46Z","receivedAt":"2022-02-23T07:47:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis patch includes no new code; it simply moves a bunch of lines into a\nnew function.  As such, there are no functional changes.  This is just a\npreparatory step to allow the printed messages to be handled differently\nby other callers, such as in `git merge-tree --write-tree`.\n\n(Patch best viewed with\n     --color-moved --color-moved-ws=allow-indentation-change\n to see that it is a simple code movement.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 78 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 37 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 9bf15a01db8..ebaed98d53a 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4272,43 +4311,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\tfclose(fp);\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n-\n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"449220","messageId":"4c8f42372dda68230563d5f4d12c3bb2878ae979.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 07/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:48Z","receivedAt":"2022-02-23T07:47:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u`, which means\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex ebaed98d53a..e1b647b0a40 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4274,6 +4274,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..ddcc39d7270 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -88,6 +89,26 @@ void merge_switch_to_result(struct merge_options *opt,\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"449221","messageId":"17b57efb71404a21b05db8284e8dfd2978e6a3dd.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 06/12] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:47Z","receivedAt":"2022-02-23T07:47:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, such as:\n  * \"Auto-merging <PATH>\"\n  * \"CONFLICT (content): ...\"\n  * \"CONFLICT (file/directory)\"\n  * etc.\nIn fact, when non-content conflicts occur (such as file/directory,\nmodify/delete, add/add with differing modes, rename/rename (1to2),\netc.), these informational messages may be the only notification the\nuser gets since these conflicts are not representable in the contents\nof the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 47 ++++++++++++++++++++++++++++----\n builtin/merge-tree.c             | 21 ++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 37 +++++++++++++++++++++++++\n 3 files changed, 97 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 2a9c91328de..25b462be14e 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n [[NEWMERGE]]\n@@ -37,18 +37,50 @@ linkgit:git-merge[1], including:\n After the merge completes, a new toplevel tree object is created.  See\n `OUTPUT` below for details.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n [[OUTPUT]]\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+For a successful merge, the output from git-merge-tree is simply one\n+line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+[[OIDTLT]]\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+[[IM]]\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n+\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -72,6 +104,9 @@ used as a part of a series of steps such as:\n        NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n        git update-ref $BRANCH1 $NEWCOMMIT\n \n+Note that when the exit status is non-zero, `NEWTREE` in this sequence\n+will contain a lot more output than just a tree.\n+\n [[DEPMERGE]]\n DEPRECATED DESCRIPTION\n ----------------------\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 2332525d8bd..831d9c77583 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -396,6 +396,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -435,18 +436,27 @@ static int real_merge(struct merge_tree_options *o,\n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -456,10 +466,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    MODE_REAL),\n \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n \t/* Parse arguments */\n+\toriginal_argc = argc - 1; /* ignoring argv[0] */\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n \tswitch (o.mode) {\n@@ -483,8 +496,12 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \tcase MODE_TRIVIAL:\n \t\texpected_remaining_argc = 3;\n+\t\t/* Removal of `--trivial-merge` is expected */\n+\t\toriginal_argc--;\n \t\tbreak;\n \t}\n+\tif (o.mode == MODE_TRIVIAL && argc < original_argc)\n+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \tif (argc != expected_remaining_argc)\n \t\tusage_with_options(merge_tree_usage, mt_options);\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 6d321652e21..719d81e7173 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -103,4 +103,41 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+anonymize_hash() {\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" \"$@\"\n+}\n+\n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n+for opt in $(git merge-tree --git-completion-helper-all)\n+do\n+\tif test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n+\tthen\n+\t\tcontinue\n+\tfi\n+\n+\ttest_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n+\t\ttest_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3\n+\t'\n+done\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"449222","messageId":"7b1ee417f3d5a4c1778da289f1b93b28dabe4a4e.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 08/12] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:49Z","receivedAt":"2022-02-23T07:47:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  9 +++++++++\n builtin/merge-tree.c             | 24 ++++++++++++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 11 +++++++++++\n 3 files changed, 42 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 25b462be14e..68a51c82618 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -58,6 +58,7 @@ line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -70,6 +71,14 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+[[CFI]]\n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n [[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 831d9c77583..3653526cc0a 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -400,7 +403,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *merge_bases = NULL;\n@@ -441,6 +445,22 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n \t\tprintf(\"\\n\");\n \t\tmerge_display_update_messages(&opt, &result);\n@@ -508,7 +528,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == MODE_REAL)\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 719d81e7173..8e6dba44288 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -117,6 +117,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -140,4 +142,13 @@ do\n \t'\n done\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"449223","messageId":"22297e6ce75fc232125798e65d0887c5b660900f.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 10/12] merge-tree: allow `ls-files -u` style info to be NUL terminated","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:51Z","receivedAt":"2022-02-23T07:47:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch as `git ls-files` has a -z option, let's add one to merge-tree so\nthat the conflict-info section can be NUL terminated (and avoid quoting\nof unusual filenames).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 21 +++++++++++++----\n builtin/merge-tree.c             |  6 +++--\n t/t4301-merge-tree-write-tree.sh | 40 ++++++++++++++++++++++++++++++++\n 3 files changed, 61 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex b89aabdb98e..75b57f8abab 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -40,6 +40,12 @@ After the merge completes, a new toplevel tree object is created.  See\n OPTIONS\n -------\n \n+-z::\n+\tDo not quote filenames in the <Conflicted file info> section,\n+\tand end each filename with a NUL character rather than\n+\tnewline.  Also begin the messages section with a NUL character\n+\tinstead of a newline.  See <<OUTPUT>> below for more information.\n+\n --name-only::\n \tIn the Conflicted file info section, instead of writing a list\n \tof (mode, oid, stage, path) tuples to output for conflicted\n@@ -76,7 +82,8 @@ OID of toplevel tree\n \n This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n-files within this tree may have embedded conflict markers.\n+files within this tree may have embedded conflict markers.  This section\n+is always followed by a newline (or NUL if `-z` is passed).\n \n [[CFI]]\n Conflicted file info\n@@ -89,20 +96,26 @@ This is a sequence of lines with the format\n The filename will be quoted as explained for the configuration\n variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n the `--name-only` option is passed, the mode, object, and stage will\n-be omitted.\n+be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n+character instead of a newline character.\n \n [[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n-This always starts with a blank line to separate it from the previous\n-sections, and then has free-form messages about the merge, such as:\n+This always starts with a blank line (or NUL if `-z` is passed) to\n+separate it from the previous sections, and then has free-form\n+messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n   * \"Failed to merge submodule <submodule> (<reason>)\"\n   * \"Warning: cannot merge binary files: <filename>\"\n \n+Note that these free-form messages will never have a NUL character\n+in or between them, even if -z is passed.  It is simply a large block\n+of text taking up the remainder of the output.\n+\n EXIT STATUS\n -----------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 4a79caf081a..767892006e3 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -445,7 +445,7 @@ static int real_merge(struct merge_tree_options *o,\n \tif (o->show_messages == -1)\n \t\to->show_messages = !result.clean;\n \n-\tputs(oid_to_hex(&result.tree->object.oid));\n+\tprintf(\"%s%c\", oid_to_hex(&result.tree->object.oid), line_termination);\n \tif (!result.clean) {\n \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n \t\tconst char *last = NULL;\n@@ -467,7 +467,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\tstring_list_clear(&conflicted_files, 1);\n \t}\n \tif (o->show_messages) {\n-\t\tprintf(\"\\n\");\n+\t\tputchar(line_termination);\n \t\tmerge_display_update_messages(&opt, &result);\n \t}\n \tmerge_finalize(&opt, &result);\n@@ -493,6 +493,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_SET_INT('z', NULL, &line_termination,\n+\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n \t\tOPT_BOOL_F(0, \"name-only\",\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 0ec5f0d3f7e..22e03f0939c 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -173,4 +173,44 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'NUL terminated conflicted file \"lines\"' '\n+\tgit checkout -b tweak1 side1 &&\n+\ttest_write_lines zero 1 2 3 4 5 6 >numbers &&\n+\tgit add numbers &&\n+\tgit mv numbers \"Αυτά μου φαίνονται κινέζικα\" &&\n+\tgit commit -m \"Renamed numbers\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\t#   \"Αυτά μου φαίνονται κινέζικα\" should have a conflict\n+\techo HASH | lf_to_nul >expect &&\n+\n+\tq_to_tab <<-EOF | lf_to_nul >>expect &&\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~tweak1\n+\t100644 HASH 2Qwhatever~tweak1\n+\t100644 HASH 1QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 2QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 3QΑυτά μου φαίνονται κινέζικα\n+\n+\tEOF\n+\n+\tcat <<-EOF >>expect &&\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n+\tCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n+\tAuto-merging Αυτά μου φαίνονται κινέζικα\n+\tCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"449224","messageId":"db73c6dd82346b55c46334111e89aefd3d9039a2.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 11/12] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:52Z","receivedAt":"2022-02-23T07:47:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 75b57f8abab..628324646d3 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -59,6 +59,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n [[OUTPUT]]\n OUTPUT\n ------\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 767892006e3..b4f5c7e0aab 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -399,6 +399,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint name_only;\n };\n@@ -434,7 +435,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tmerge_bases = get_merge_bases(parent1, parent2);\n-\tif (!merge_bases)\n+\tif (!merge_bases && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tmerge_bases = reverse_commit_list(merge_bases);\n \n@@ -499,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 22e03f0939c..bd1769c624b 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -44,7 +44,13 @@ test_expect_success setup '\n \tgit checkout side3 &&\n \tgit mv numbers sequence &&\n \ttest_tick &&\n-\tgit commit -m rename-numbers\n+\tgit commit -m rename-numbers &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Clean merge' '\n@@ -213,4 +219,20 @@ test_expect_success 'NUL terminated conflicted file \"lines\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"449225","messageId":"f1231a8fbc8cfd901ebf9f7e3d86b6d5655778d5.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 09/12] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:50Z","receivedAt":"2022-02-23T07:47:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide a --name-only option for users to exclude the mode, oid, and\nstage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 34 ++++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++--\n 3 files changed, 62 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 68a51c82618..b89aabdb98e 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -40,6 +40,13 @@ After the merge completes, a new toplevel tree object is created.  See\n OPTIONS\n -------\n \n+--name-only::\n+\tIn the Conflicted file info section, instead of writing a list\n+\tof (mode, oid, stage, path) tuples to output for conflicted\n+\tfiles, just provide a list of filenames with conflicts (and\n+\tdo not list filenames multiple times if they have multiple\n+\tconflicting stages).\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -58,7 +65,7 @@ line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -72,19 +79,24 @@ working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n [[CFI]]\n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--name-only` option is passed, the mode, object, and stage will\n+be omitted.\n \n [[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -116,6 +128,16 @@ used as a part of a series of steps such as:\n Note that when the exit status is non-zero, `NEWTREE` in this sequence\n will contain a lot more output than just a tree.\n \n+For conflicts, the output includes the same information that you'd get\n+with linkgit:git-merge[1]:\n+\n+  * what would be written to the working tree (the\n+    <<OIDTLT,OID of toplevel tree>>)\n+  * the higher order stages that would be written to the index (the\n+    <<CFI,Conflicted file info>>)\n+  * any messages that would have been printed to stdout (the\n+    <<IM,Informational messages>>)\n+\n [[DEPMERGE]]\n DEPRECATED DESCRIPTION\n ----------------------\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 3653526cc0a..4a79caf081a 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -400,6 +400,7 @@ enum mode {\n struct merge_tree_options {\n \tint mode;\n \tint show_messages;\n+\tint name_only;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -453,7 +454,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->name_only)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -488,6 +493,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F(0, \"name-only\",\n+\t\t\t   &o.name_only,\n+\t\t\t   N_(\"list filenames without modes/oids/stages\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 8e6dba44288..0ec5f0d3f7e 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -65,6 +65,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(git rev-parse AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -108,7 +109,7 @@ anonymize_hash() {\n }\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \t# Expected results:\n@@ -143,7 +144,7 @@ do\n done\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -151,4 +152,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"449226","messageId":"d58a7c7a9f6495467b5c6844e8557990b1f1df27.1645602413.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v6 12/12] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-02-23T07:46:53Z","receivedAt":"2022-02-23T07:47:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++\n 1 file changed, 46 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 628324646d3..ee8125810e6 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -156,6 +156,52 @@ with linkgit:git-merge[1]:\n   * any messages that would have been printed to stdout (the\n     <<IM,Informational messages>>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <<CFI,Conflicted file info>> section instead.\n+Not only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean\n+merge; check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <<CFI,Conflicted file info>> list.  The information there is\n+insufficient to do so.  For example: Rename/rename(1to2) conflicts (both\n+sides renamed the same file differently) will result in three different\n+file having higher order stages (but each only has one higher order\n+stage), with no way (short of the <<IM,Informational messages>> section)\n+to determine which three files are related.  File/directory conflicts\n+also result in a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<<IM,Informational messages>> section has the necessary info, though it\n+is not designed to be machine parseable.\n+\n+Do NOT assume all filenames listed in the <<IM,Informational messages>>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <<CFI,Conflicted file info>> and\n+re-merging them to present the conflicts to the user.  This will lose\n+information.  Instead, look up the version of the file found within the\n+<<OIDTLT,OID of toplevel tree>> and show that instead.  In particular,\n+the latter will have conflict markers annotated with the original\n+branch/commit being merged and, if renames were involved, the original\n+filename.  While you could include the original branch/commit in the\n+conflict marker annotations when re-merging, the original filename is\n+not available from the <<CFI,Conflicted file info>> and thus you would\n+be losing information that might help the user resolve the conflict.\n+\n [[DEPMERGE]]\n DEPRECATED DESCRIPTION\n ----------------------\n-- \ngitgitgadget\n"},{"id":"449335","messageId":"xmqqczjduz2h.fsf@gitster.g","threadId":"57288","inReplyTo":"CABPp-BE+DaBkis0r7pqs-kaChCvFhCEsyDg=gs3=QjWOPERaXQ@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-23T20:07:18Z","receivedAt":"2022-02-23T20:07:26Z","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> The objection you are arguing against is not my position.  In fact,\n> I'm not even objecting to having a single-step cherry-pick, I'm\n> objecting to providing it _now_, which I thought would have been clear\n> from the portion of my email you snipped (\"...I'm happy to add [a\n> single step cherry-pick primitive] along with the tool I submit\n> later...\").\n\nThe entry point into the in-core merge machinery of ort already\nknows how to accept externally defined merge-base(s) to bypass the\n\"caller didn't give us the merge base, so let's figure them out by\nusing the two heads being merged\" logic, so it just felt backwards\n*not* to have a vanilla three-way merge that can take three trees\nand be used for merge, cherry-pick or revert as the single primitive\nin the very beginning before we talk about multi-step operations.\n\nSo, I guess I am still not getting where the \"I'm happy to _add_\"\npart comes from.  If we start with a primitive (internally callable\nfrom C) \"here are three trees O A B, do the three-way merge\", then\nthere is nothing to \"add\" at all to expose a single-step\ncherry-pick.  In fact, to the users of merge-tree, the result does\nnot have to have any fixed meaning.  If they pass common ancestors\nas the merge bases as Os and the current HEAD and the other branch\nas A and B, they get a merge.  If they pass the commit to be picked\nas B, current HEAD as A and B's parent as O, they get a cherry-pick.\n\nPerhaps starting from \"You are allowed to give me two commits A B,\nand I do not let you specify the commit O to use as a common\n'ancestor'\" is the root cause of making this thing feel backwards.\nI agree with the goal of having an all-incore machinery that can do\na merge.  I just do not see the reason why you have to build it in a\nway that cannot be reused for other two directions of merge-y\noperations.\n\n"},{"id":"449365","messageId":"xmqqsfs9p46b.fsf@gitster.g","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 00/12] In-core git merge-tree (\"Server side merges\")","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-23T23:13:32Z","receivedAt":"2022-02-23T23:13:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> == Updates Log ==\n>\n> Stuff NOT included that reviewers brought up in various rounds (and which\n> might still be an open question):\n\nExcept for them, the differences since the last round all looked\nsensible to me.\n\nWill queue.\n"},{"id":"449382","messageId":"CABPp-BEsYTz35XpXy_j09J9-ke4UoCTED4z3L1sq0vYHuvuKPQ@mail.gmail.com","threadId":"57288","inReplyTo":"xmqqczjduz2h.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-24T02:22:00Z","receivedAt":"2022-02-24T02:22:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Feb 23, 2022 at 12:07 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n>\n> > The objection you are arguing against is not my position.  In fact,\n> > I'm not even objecting to having a single-step cherry-pick, I'm\n> > objecting to providing it _now_, which I thought would have been clear\n> > from the portion of my email you snipped (\"...I'm happy to add [a\n> > single step cherry-pick primitive] along with the tool I submit\n> > later...\").\n>\n> The entry point into the in-core merge machinery of ort already\n> knows how to accept externally defined merge-base(s) to bypass the\n> \"caller didn't give us the merge base, so let's figure them out by\n> using the two heads being merged\" logic, so it just felt backwards\n> *not* to have a vanilla three-way merge that can take three trees\n> and be used for merge, cherry-pick or revert as the single primitive\n> in the very beginning before we talk about multi-step operations.\n\n\"The entry point\"?\n\nThere are two entry points: merge_incore_recursive(), and\nmerge_incore_nonrecursive().  The former is analogous to\nmerge-recursive's merge_recursive(), and the latter is analogous to\nmerge_trees().\n\n\"git merge\" always uses merge_incore_recursive()/merge_recursive().\nThe other big merge-y operations (cherry-pick, rebase, revert), always\nuse merge_incore_nonrecursive()/merge_trees().\n\n(Technically, merge-recursive has a third entry point used by am &\nstash, but let's ignore it for a moment.)\n\n> So, I guess I am still not getting where the \"I'm happy to _add_\"\n> part comes from.  If we start with a primitive (internally callable\n> from C) \"here are three trees O A B, do the three-way merge\", then\n> there is nothing to \"add\" at all to expose a single-step\n> cherry-pick.  In fact, to the users of merge-tree, the result does\n> not have to have any fixed meaning.  If they pass common ancestors\n> as the merge bases as Os and the current HEAD and the other branch\n> as A and B, they get a merge.  If they pass the commit to be picked\n> as B, current HEAD as A and B's parent as O, they get a cherry-pick.\n>\n> Perhaps starting from \"You are allowed to give me two commits A B,\n> and I do not let you specify the commit O to use as a common\n> 'ancestor'\" is the root cause of making this thing feel backwards.\n> I agree with the goal of having an all-incore machinery that can do\n> a merge.  I just do not see the reason why you have to build it in a\n> way that cannot be reused for other two directions of merge-y\n> operations.\n\nSo, am I correct to understand that what bugs you is actually\nmerge-recursive's and merge-ort's API?  That you don't want these two\ntypes of merges to have different entry points, and that there should\nin fact only be one?\n\nThat might be an interesting line of investigation to try to modify\nthe API to achieve that, but that feels like a bigger task that is\nsomewhat tangential to this series.\n\nWithout such a thing, handling both merging-of-divergent-branches and\nthree-way-merge-with-specified-merge-base would be separate codepaths\nthat call different functions.  I implemented one of those two\ncodepaths, and if we want the other then it needs to be added.  That's\nwhere the \"add\" part comes from, in my view.\n\n\nDoes that help, or am I still missing what you're saying?\n"},{"id":"449501","messageId":"xmqqee3skp3x.fsf@gitster.g","threadId":"57288","inReplyTo":"CABPp-BEsYTz35XpXy_j09J9-ke4UoCTED4z3L1sq0vYHuvuKPQ@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-24T20:04:50Z","receivedAt":"2022-02-24T20:04:56Z","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> So, am I correct to understand that what bugs you is actually\n> merge-recursive's and merge-ort's API?  That you don't want these two\n> types of merges to have different entry points, and that there should\n> in fact only be one?\n\nIt is more like\n\n    It is more than OK that there are two, but the basic primitive\n    is the \"we have this and that tree objects to merge, and use\n    this tree object as the ancestor\" non-recursive thing, with the\n    recursive one being just a thin wrapper around it to compute\n    common ancestors, using the three-way primitive to reduce them\n    into a single virtual ancestor, and finally using the three-way\n    primitive to come up with the final result.\n\nAnd making the composite \"recursive\" feature available long before\nthe underlying \"non-recursive\" primitive becomes easily accessible\nto the scripters and system builders simply felt backwards.\n"},{"id":"449534","messageId":"xmqqh78nj0q0.fsf@gitster.g","threadId":"57288","inReplyTo":"xmqqee3skp3x.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-24T23:36:55Z","receivedAt":"2022-02-24T23:37:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> It is more like\n> ...\n\nActually, I misspoke.  It is a bit different.\n\nIn my mind, the building block hierarchy would have been\n\n (1) Take three tree objects A, B, and O, and do the three-way\n     merge.  No history relation is assumed among them.\n\n (2) Take two tree objects A and B, with one or more commit objects\n     Os; use (2) recursively to reduce Os into a single O and then\n     apply (1) on A, B and O.\n\n (3) Take two commit objects A and B.  Compute Os out of A and B and\n     use (2) once to merge A and B.\n\nI think the basic primitive that should be exposed to an external\nworld (read: plumbing) this year, after all years of experience with\nmerge-recursive, should be (2), not (1).  \n\nIf you have (2), then (3) is trivially possible (it is just a single\ncall to get_merge_base()).  \"git merge-tree A B\" without having to\nspell out bases is so convenent and you do not have to write\n\"git merge-tree A B $(git merge-base --all A B)\", so I am OK for it\nto exist, but it is not essential.\n\nIf you have (2) and exposed (2) as the primitive plumbing,\ncherry-pick and revert would be a narrow special case of passing\nonly one O to the machinery.\n\nAnd coming from the above point of view, exposing (3) as the\nprimitive plumbing to scripters and system builders, and later\nhaving to _add_ support to allow (2), felt backwards.  It should be\ntrivial for us to make (2) available before we can even offer (3),\nbut what is happening to this new plumbing command goes in the\nopposite order.\n\nIt may be, as you said, the problem the underlying ort API has that\nsomehow makes it harder to expose (2), in which case, yes, I think\nthat is what bugs me.\n\n\n\n"},{"id":"449594","messageId":"nycvar.QRO.7.76.6.2202251706150.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BG7id0GfpDee_7ETZ_94BC_i-e_=-u=PrYJeD7d4sVbiw@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-25T16:26:09Z","receivedAt":"2022-02-25T16:26:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Tue, 22 Feb 2022, Elijah Newren wrote:\n\n> On Tue, Feb 22, 2022 at 8:54 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Mon, 21 Feb 2022, Johannes Schindelin wrote:\n> >\n> > > [...] since `merge-tree` is a low-level tool meant to be called by\n> > > programs rather than humans, we need to make sure that those messages\n> > > remain machine-parseable, even if they contain file names.\n> > >\n> > > [...]\n> > >\n> > > Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n> >\n> > Or maybe much better: use NUL to separate those messages if `-z` is passed\n> > to `merge-tree`? That would address the issue in one elegant diff.\n> >\n> > What do you think?\n>\n> Separating the combination of messages for a single target path from\n> the combination of messages for a different target path by a NUL\n> character may make sense.  Would we also want the messages for a path\n> to be prepended by the pathname and a NUL character, in this case, to\n> make it easier to determine which path the group of messages are for?\n>\n> I'm not sure if that does exactly what you are asking, though.\n\nThe most important thing I am asking for is a way for a program calling\n`merge-tree` to figure out whether it knows how to handle all the types of\nconflicts encountered in this merge.\n\nSo that a web UI could present e.g. simple content conflicts, and even\nrename/rename conflicts, but would know that it cannot resolve the\nconflicts e.g. when a submodule is affected.\n\nSo... I am fairly certain that we're not as close to addressing this as I\nhad hoped for.\n\n> The thing that is stored (in opt->priv->output) is a strbuf per path,\n> not an array of strbufs per path.  So, if we have a rename/delete and\n> a rename/add and a mode conflict for the same \"path\" (A->B on one\n> side, other side deletes A and adds a symlink B, resulting in three\n> messages for path \"B\" that are all appended into a single strbuf),\n> then we'll have a single \"message\" which has three newlines.  We can\n> add a NUL character at the end of that, but not between the messages\n> without restructuring things a bit.\n>\n> There's also at least one example, with submodules, where there are\n> two path_msg() calls for the same individual conflict in order to\n> split conflict info from resolution advice, and those really shouldn't\n> be thought of as messages for different conflicts.  (I'm starting to\n> wonder if the resolution advice should just be tossed; I kept it\n> because merge-recursive had it, but it might not make sense with\n> merge-ort being used by server side merges.  But even if we toss that\n> one, I'm not sure I want to commit to one path_msg() call per \"logical\n> conflict\".)\n>\n> But...maybe this would be good enough for some kind of use you have?\n> Because if you only want to care about \"simple\" cases, you could\n> potentially define those as ones with only one newline  in them.\n\nWe cannot rely on newline character parsing because that is a valid\nfilename character on Unix:\n\n\t$ echo a >'with\n\t> a newline'\n\n\t$ ls -la with*\n\t-rw-r--r-- 1 me me 2 Feb 25 17:10 'with'$'\\n''a newline'\n\nI guess we have to work harder on this and add more than just an `strbuf`\nso that we can output `<path>NUL<conflict-type>NUL` pairs (where we\npromise to keep the `<conflict-type>` strings constant) or something\nsimilar.\n\nCiao,\nDscho\n"},{"id":"449596","messageId":"nycvar.QRO.7.76.6.2202251726500.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BFG_05RyVVyiHzOkuoT8=9NftJGp_W+DXd7ktqC5UfvwQ@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-02-25T16:31:19Z","receivedAt":"2022-02-25T16:31:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Tue, 22 Feb 2022, Elijah Newren wrote:\n\n> Sidenote: Do you lump in binary merge conflicts with \"non-semantic\n> merge conflicts\"?  You would by your definition, but I'm not sure it\n> matches.\n>\n> I tend to call things either content-based conflicts or path-based\n> conflicts, where content-based usually means textual-based but also\n> includes merges of binaries.\n\nI like \"content-based conflicts\".\n\nAnd no, I had not even thought about binary merge conflicts yet...\n\n> On Mon, Feb 21, 2022 at 2:46 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > Concretely: while I am not currently aware of any web UI that allows\n> > to resolve simple rename/rename conflicts, it is easily conceivable\n> > how to implement such a thing. When that happens, we will need to be\n> > able to teach the server-side code to discern between the cases that\n> > can be handled in the web UI (trivial merge conflicts, trivial\n> > rename/rename conflicts) as compared to scenarios where the conflicts\n> > are just too complex.\n>\n> Um, I'm really worried about attempting to make the conflict notices\n> machine parseable.  I don't like that idea at all, and I even tried to\n> rule that out already with my wording:\n> \"\"\"\n> In all cases, the\n> <Informational messages> section has the necessary info, though it is\n> not designed to be machine parseable.\n> \"\"\"\n> though maybe I should have been even more explicit.  The restrictions\n> that those messages be stable is too rigid, I think.  I also think\n> they're a poor way to communicate information to a higher level tool.\n> I would much rather us add some kind of additional return data\n> structures from merge ort and use them if we want extra info.\n\nOkay.\n\nI thought that we could keep the `CONFLICT (<type>)` constant enough to\nserve as such a machine-parseable thing. And then presenting\n`<path>NUL<message>NUL` could have served my use case well...\n\n> > Here's an excerpt from t4301:\n> >\n> > -- snip --\n> > Auto-merging greeting\n> > CONFLICT (content): Merge conflict in greeting\n> > Auto-merging numbers\n> > CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n> > CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n> > -- snap --\n> >\n> > This is the complete set of messages provided in the `test conflict\n> > notices and such` test case.\n> >\n> > I immediately notice that every line contains at least one file name.\n> > Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\n> > does not seem as if the file names are quoted:\n> >\n> >                 path_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n> >\n> > (where `path` is used verbatim in a call to `merge_3way()` before that,\n> > i.e. it must not have been quoted)\n> >\n> > I would like to register a wish to ensure that file names with special\n> > characters (such as most notably line-feed characters) are quoted in these\n> > messages, so that a simple server-side parser can handle messages starting\n> > with `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n> > \"throw the hands up in the air\" if any other message prefix is seen.\n> >\n> > Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n> > For the `Auto-merging` one, it would be trivial, but I fear that we will\n> > have to work a bit on the `path_msg()` function\n> > (https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\n> > accepts a variable list of arguments without any clue whether the\n> > arguments refer to paths or not. (And I would be loathe to switch _all_\n> > callers to do the quoting themselves.)\n> >\n> > I see 28 calls to that function, and at least a couple that pass not only\n> > a path but also an OID (e.g.\n> > https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n> >\n> > We could of course be sloppy and pass even OIDs through\n> > `sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\n> > special characters in them, but it gets more complicated e.g. in\n> > https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\n> > pass an `strbuf` that contains a somewhat free-form commit message.\n> >\n> > I guess we could still pass those through `sq_quote_buf_pretty()`, even if\n> > they are not paths, to ensure that there are no special characters in the\n> > machine-parseable lines.\n> >\n> > What do you think?\n>\n> Switching to single quoting paths as a matter of style might make\n> sense, but only if we go through and change every caller to do so so\n> that we can make sure it applies to all paths.  And only paths and not\n> OIDs.\n\nYes, that sounds unappealing.\n\n> But I'm going to reserve the right in merge-ort to modify, add, or\n> delete any of those messages passed to path_msg(), which might wreak\n> havoc on your attempts to parse those strings.  I think they're a bad\n> form for communicating information to a script or program, and trying\n> to transform them into such risks making them suboptimal at\n> communicating info to humans.  These messages should optimize the\n> latter, and if we want something for the former, it should probably be\n> a new independent bit of info.\n\nMakes sense.\n\nSo we need something in addition to those messages.\n\nCiao,\nDscho\n"},{"id":"449619","messageId":"xmqqh78mdc31.fsf@gitster.g","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202251726500.11118@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-02-25T18:40:18Z","receivedAt":"2022-02-25T18:40:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> I tend to call things either content-based conflicts or path-based\n>> conflicts, where content-based usually means textual-based but also\n>> includes merges of binaries.\n>\n> I like \"content-based conflicts\".\n\nYup, even before ort existed, we had clear distinction between tree\nlevel merges (i.e. which path corresponds to which other path, which\nis done in unpack_trees() and \"read-tree O A B\") and content level\nmerges (which is done with ll_merge()).\n\n>> Switching to single quoting paths as a matter of style might make\n>> sense, but only if we go through and change every caller to do so so\n>> that we can make sure it applies to all paths.  And only paths and not\n>> OIDs.\n>\n> Yes, that sounds unappealing.\n>\n>> But I'm going to reserve the right in merge-ort to modify, add, or\n>> delete any of those messages passed to path_msg(), which might wreak\n>> havoc on your attempts to parse those strings.  I think they're a bad\n>> form for communicating information to a script or program, and trying\n>> to transform them into such risks making them suboptimal at\n>> communicating info to humans.  These messages should optimize the\n>> latter, and if we want something for the former, it should probably be\n>> a new independent bit of info.\n>\n> Makes sense.\n\nAs long as it is made clear that the path_msg() is not for machine\nconsumption (perhaps we can sprinkle <RED><RESET> at random places\nto make it impossible for machines to handle), I think the direction\nmakes sense, too ;-)\n"},{"id":"449659","messageId":"CABPp-BGnqXdFBNAyKRXgvCHv+aUZTMg-CgcQf95dKAR-e1zSjQ@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2202251726500.11118@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-02-26T06:53:28Z","receivedAt":"2022-02-26T06:53:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Fri, Feb 25, 2022 at 8:31 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Tue, 22 Feb 2022, Elijah Newren wrote:\n>\n> > Sidenote: Do you lump in binary merge conflicts with \"non-semantic\n> > merge conflicts\"?  You would by your definition, but I'm not sure it\n> > matches.\n> >\n> > I tend to call things either content-based conflicts or path-based\n> > conflicts, where content-based usually means textual-based but also\n> > includes merges of binaries.\n>\n> I like \"content-based conflicts\".\n>\n> And no, I had not even thought about binary merge conflicts yet...\n>\n> > On Mon, Feb 21, 2022 at 2:46 AM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> > >\n> > > Concretely: while I am not currently aware of any web UI that allows\n> > > to resolve simple rename/rename conflicts, it is easily conceivable\n> > > how to implement such a thing. When that happens, we will need to be\n> > > able to teach the server-side code to discern between the cases that\n> > > can be handled in the web UI (trivial merge conflicts, trivial\n> > > rename/rename conflicts) as compared to scenarios where the conflicts\n> > > are just too complex.\n> >\n> > Um, I'm really worried about attempting to make the conflict notices\n> > machine parseable.  I don't like that idea at all, and I even tried to\n> > rule that out already with my wording:\n> > \"\"\"\n> > In all cases, the\n> > <Informational messages> section has the necessary info, though it is\n> > not designed to be machine parseable.\n> > \"\"\"\n> > though maybe I should have been even more explicit.  The restrictions\n> > that those messages be stable is too rigid, I think.  I also think\n> > they're a poor way to communicate information to a higher level tool.\n> > I would much rather us add some kind of additional return data\n> > structures from merge ort and use them if we want extra info.\n>\n> Okay.\n>\n> I thought that we could keep the `CONFLICT (<type>)` constant enough to\n> serve as such a machine-parseable thing.\n\nThat _probably_ is, but I thought you wanted to parse all N paths\nembedded in the message after that part as well?\n\n> And then presenting\n> `<path>NUL<message>NUL` could have served my use case well...\n\nWould it?  Wouldn't you need something more like\n\n<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL\n ?\n\nI mean, if rename/rename is what you want to handle, there are three\npaths in that message.  And you need to know all three paths in order\nto combine the relevant parts of the <Conflicted File Info> section\ntogether.\n\n(Also, while we're at it, I decided to throw a stable short-type\ndescription string (e.g. \"CONFLICT (rename/rename)\") in there, which\nwill _probably_ be the first part of the message string but still\nallow us to change the message string later if we want.)\n\n\nAlso, we'd want those parsing this information to keep in mind that:\n  * Any given conflict can affect multiple paths\n  * Any path can be part of multiple conflicts\n  * (The above two items imply a potentially many-to-many relationship\nbetween paths and conflicts)\n  * Paths listed in these logical conflicts may not correspond to a\nfile in the index (they could be a directory, or file that was in a\nprevious version)\n  * Some of these \"logical conflicts\" are not actually conflicts but\njust notices (e.g. \"auto-merging\" or \"submodule updated\" or \"WARNING\"\nor \"<submodules are weird>\" messages)\n\nand we'd have to do some work to make sure the paths in the given\nmessages lined up with the files actually recorded in the index (e.g.\nwith distinct types we rename both files to avoid the collision, but\nprint the conflict notice for the original path rather than the new\npaths)\n\n[...]\n> > But I'm going to reserve the right in merge-ort to modify, add, or\n> > delete any of those messages passed to path_msg(), which might wreak\n> > havoc on your attempts to parse those strings.  I think they're a bad\n> > form for communicating information to a script or program, and trying\n> > to transform them into such risks making them suboptimal at\n> > communicating info to humans.  These messages should optimize the\n> > latter, and if we want something for the former, it should probably be\n> > a new independent bit of info.\n>\n> Makes sense.\n>\n> So we need something in addition to those messages.\n\nYes.  Does the proposal above sound like it'd cover your needs?  If\nso, we'd probably need to go through all the callers to path_msg() and\neither add an immediate call to another function immediately\nafterwards that stores this additional information or somehow change\nthe path_msg() call itself to somehow take an additional arbitrary\nlist of arguments representing the paths and short-desc we want to\nstore somewhere.\n"},{"id":"449698","messageId":"20220227173517.qrosjw75y3bcbglt@gmail.com","threadId":"57288","inReplyTo":"CABPp-BE+DaBkis0r7pqs-kaChCvFhCEsyDg=gs3=QjWOPERaXQ@mail.gmail.com","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Johannes Altmanninger","fromEmail":"aclopte@gmail.com","sentAt":"2022-02-27T17:35:17Z","receivedAt":"2022-02-27T17:35:26Z","isPatch":true,"sender":{"key":"aclopte@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6853872?v=4"},"body":"On Tue, Feb 22, 2022 at 08:26:41AM -0800, Elijah Newren wrote:\n> On Mon, Feb 21, 2022 at 10:55 AM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > Elijah Newren <newren@gmail.com> writes:\n> >\n> > > Adding such an ability to merge-tree would be trivial -- it basically\n> > > involves just two things: (1) accepting one extra argument, and (2)\n> > > calling merge_incore_nonrecursive() instead of\n> > > merge_incore_recursive().\n> > >\n> > > However, I think forking a subprocess for every merge of a series of\n> > > commits is a completely unreasonable overhead, so even if we provide\n> > > such an option to merge-tree, I still want a separate plumbing-ish\n> > > tool that does non-worktree/non-index replaying of commits which is\n> > > not written as a driver of merge-tree.  That other tool should just\n> > > call merge_incore_nonrecursive() directly.  And such a tool, since it\n> > > should handle an arbitrary number of commits, should certainly be able\n> > > to handle just one commit.  From that angle, it feels like adding\n> > > another mode to merge-tree would just be a partial duplication of the\n> > > other tool.\n\nI don't think \"to avoid duplication\" is a good argument for making this\nplumbing command less flexible, because that's just chasing a local minimum\nw.r.t. redundancy. More general APIs will lead to a global minimum.\n\n> >\n> > The above does not make much sense to me.\n> >\n> > I am hearing that \"multi-step cherry-picks and reverts need to be\n> > fast and we need something like sequencer that is all written in C,\n> \n> Yes, I agree with that part so far.  jj is kicking our butt on rebase\n> speed; I'm not sure if we can catch it, but it'd be nice to see us not\n> be more than a hundred times slower.\n> \n> > and single-step cherry-pick is merely a special case that does not\n> > deserve a plumbing\".\n> \n> Well, apparently I failed at communication if that's what you heard.\n> Perhaps I can step back and provide my high-level goals, and then\n> mention how this series fits in.  My high-level goals:\n> \n>   * new sequencer-like replay tool, including multiple abilities\n> today's rebase/cherry-pick tools don't have\n>   * enable folks to use merging machinery for server side operations\n> (merge, rebase, cherry-pick, revert)\n>   * do not repeat or encourage the rebase-as-shell-script mistakes of yesteryear\n>   * somehow split this up into reviewable chunks\n> \n> Now, in particular, the \"merge divergent branches\" piece seemed like a\n> really simple portion of the problem space for which I could get some\n> early feedback without having to address the whole problem space all\n> at once, and which doesn't seem to have any downside risk.\n> \n> And even with my attempt to narrow it in scope, and even despite lots\n> of early feedback from the Git Virtual Contributor Summit six months\n> ago, it's been nearly two months of active discussions including all\n> kinds of intrinsic and tangential points about the UI and design.  Why\n> try to prematurely widen the scope?  Can we just focus on merging\n> divergent branches for now, and cover the rest later?\n> \n> > But that argument leads to \"and the same something-like-sequencer\n> > that is all written in C would need '--rebase-merges' that can pick\n> > multi-step merge sequences, and single-step merge does not deserve a\n> > plumbing\", which is an argument against this topic that is utterly\n> > absurd.\n> >\n> > So why isn't your objection not equally absurd against having a\n> > single step cherry-pick or revert primitive as a plumbing?\n> \n> The objection you are arguing against is not my position.  In fact,\n> I'm not even objecting to having a single-step cherry-pick, I'm\n> objecting to providing it _now_, which I thought would have been clear\n> from the portion of my email you snipped (\"...I'm happy to add [a\n> single step cherry-pick primitive] along with the tool I submit\n> later...\").  Since that wasn't clear, and since that wasn't my only\n> communication failure here, let me attempt to be clearer about my\n> objection(s):\n> \n> 1. I'm really trying to pick off a small piece of the problem space\n> and get feedback on it without unnecessarily complicating things with\n> unrelated issues.  Thus, this series is _only_ about merging branches\n> that have diverged, and leaves commit replaying for later.\n> \n> 2. Two folks have chimed in about the single step cherry-pick, and the\n> ONLY reason given for wanting such a thing was to create a\n> rebasing/cherry-picking script which was driven by repeatedly invoking\n> this low-level primitive command.  That's also the only usecase I can\n> currently think of for such a primitive.  To me, that means providing\n> such a low-level command now would be likely to result in the\n> rebase-as-a-script mistake of yesteryear.  I think we can avoid that\n> pitfall by first providing a tool that avoids the\n> repeatedly-fork-git-subprocesses model.  (Also, providing a low-level\n> single-step cherry-pick command also has the added negative of further\n> distracting from the focus on merging divergent branches.)\n\nI agree that it's not a good idea to call merge-tree in a loop for\ncherry-picking commit sequences.\n\nAt the same time, it is weird for such a low-level tool to not allow\nspecifying merge bases.\nAccepting merge bases is the more logical API, that might allow curious\nusers to figure out how revert/cherry-pick are implemented.\n\nI intuitively prefer the version that accepts merge bases but I don't have\na good use case, so I think it's okay to add that later if we ever find use\nfor it.\n\n> \n> 3. The merge primitive in this series is useful completely independent\n> of any rebasing script (it would not be used solely for rebasing\n> merges, if it's used for that purpose at all, as evidenced by the fact\n> that dscho is already trying to use it for doing new real merges).\n> \n> 4. Once we have a git-replay tool that can replay a sequence of\n> commits, there _might_ not be a need for a single commit replaying\n> primitive.  If we provided one as you and Johannes Altimanninger were\n> asking for, and it turned out to be deemed useless because the later\n> tool I provide can do everything it can and more, haven't we just\n> wasted time in providing it?  And perhaps also wasted future time as\n> we then have work to do to deprecate and remove the new command or\n> mode? (NOTE: I did *not* say there was \"no need\" for a single-commit\n> replaying primitive -- I said there \"might not\" be a need.)\n\nIf we get a tool that can do multiple cherry-picks, I think there is no\ntechnical reason against having an equivalent tool that can do multiple merges.\nIn that future, merge-tree might be mostly obsolete.\n\nIn general, this is a difficult discussion.  It's really hard to judge\nthis series without a bigger picture of how our future UI will look like.\n(Thanks for sharing the replay code BTW, there are some nice features in\nthere.)  Though I agree that integrating this (minimal) series first makes\na ton of sense, because it already supports a valid use case.\n\nI feel like the output format is a bit experimental because it doesn't give\nmuch of the conflict information in a machine-parseable format.  Of course\nit's good enough for many uses (so I don't think this should block this\ntopic) but I think we should have a plan on how to change the output format\nin future without adding ugly compatibility hacks. Marking merge-tree as\n\"experimental\" (like git-switch/git-restore) comes to mind.  That would work\nalthough it's not the most user-friendly way.\n\n(OK I just saw that you are still looking into the output format in\nCABPp-BG++YqesTxp+JL3XzwrogfMag1NscoMpCOExmV9z6Py9A@mail.gmail.com )\n\nI wanted to implement some (cherry-picking) scripts using merge-tree but I\ndon't have enough time or need, so I don't have much feedback on the output\nformat today. I can imagine that it would be nice to have a clear distinction\nbetween content conflicts and non-content conflicts, but let's worry about\nthat later..\n\n> \n> Also, since you bring up --rebase-merges, there's an additional point\n> about it that might be relevant:\n> \n> 5. While you could implement a naive --rebase-merges in terms of a\n> primitive for merging divergent branches (or vice-versa, i.e.\n> implement merging divergent branches from a naive --rebase-merges\n> implementation), I think replaying merges more intelligently[*] is\n> actually a distinct operation from doing a new merge of divergent\n> branches and that you probably can't implement one in terms of the\n> other.  (I'm not certain on this, and definitely don't want to argue\n> the finer points on it while my implementation is still half-baked,\n> but I really do think they are different things right now.)\n> \n> [*] https://lore.kernel.org/git/CABPp-BHp+d62dCyAaJfh1cZ8xVpGyb97mZryd02aCOX=Qn=Ltw@mail.gmail.com/\n"},{"id":"449699","messageId":"20220227173522.wfshhu2rsyss576e@gmail.com","threadId":"57288","inReplyTo":"xmqqh78nj0q0.fsf@gitster.g","subject":"Re: [PATCH v3 04/15] merge-tree: implement real merges","fromName":"Johannes Altmanninger","fromEmail":"aclopte@gmail.com","sentAt":"2022-02-27T17:35:22Z","receivedAt":"2022-02-27T17:35:31Z","isPatch":true,"sender":{"key":"aclopte@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6853872?v=4"},"body":"On Thu, Feb 24, 2022 at 03:36:55PM -0800, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > It is more like\n> > ...\n> \n> Actually, I misspoke.  It is a bit different.\n> \n> In my mind, the building block hierarchy would have been\n> \n>  (1) Take three tree objects A, B, and O, and do the three-way\n>      merge.  No history relation is assumed among them.\n> \n>  (2) Take two tree objects A and B, with one or more commit objects\n>      Os; use (2) recursively to reduce Os into a single O and then\n>      apply (1) on A, B and O.\n\nAccepting multiple bases is nice (because it frees users of having\nto recursively merge their merge-bases),\n\nLet's say we take this series in its current form:\n\n\tgit merge-tree [--write-tree] [<options>] <branch1> <branch2>\n\tgit merge-tree [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n\nand later discover we want to add (2), one possible syntax would be\n\n\tgit merge-tree --write-tree [<options>] <branch1> <branch2> <base>...\n\n(or put the bases in the middle like merge-file).\nThough the mandatory --write-tree leaves a bad taste.\n\nA separate option is a better alternative:\n\n\tgit merge-tree [--write-tree] [<options>] --base=<base1>,<base2>,... <branch1> <branch2>\n\nAnyway, no need to worry about that now, especially since the root cause of\nthe ugliness is the legacy --trivial-merge, and there is no way avoid that,\neven if we add this now.\n\n> \n>  (3) Take two commit objects A and B.  Compute Os out of A and B and\n>      use (2) once to merge A and B.\n> \n> I think the basic primitive that should be exposed to an external\n> world (read: plumbing) this year, after all years of experience with\n> merge-recursive, should be (2), not (1).  \n> \n> If you have (2), then (3) is trivially possible (it is just a single\n> call to get_merge_base()).  \"git merge-tree A B\" without having to\n> spell out bases is so convenent and you do not have to write\n> \"git merge-tree A B $(git merge-base --all A B)\", so I am OK for it\n> to exist, but it is not essential.\n> \n> If you have (2) and exposed (2) as the primitive plumbing,\n> cherry-pick and revert would be a narrow special case of passing\n> only one O to the machinery.\n> \n> And coming from the above point of view, exposing (3) as the\n> primitive plumbing to scripters and system builders, and later\n> having to _add_ support to allow (2), felt backwards.  It should be\n> trivial for us to make (2) available before we can even offer (3),\n> but what is happening to this new plumbing command goes in the\n> opposite order.\n> \n> It may be, as you said, the problem the underlying ort API has that\n> somehow makes it harder to expose (2), in which case, yes, I think\n> that is what bugs me.\n> \n> \n> \n"},{"id":"449731","messageId":"220228.86sfs35ojn.gmgdl@evledraar.gmail.com","threadId":"57288","inReplyTo":"CABPp-BFc=hcWz1BMW7fAR=Zp3fQ3vxvBtnSYESreYwef_v1K5g@mail.gmail.com","subject":"Re: machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-28T08:50:08Z","receivedAt":"2022-02-28T09:27:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Feb 22 2022, Elijah Newren wrote:\n\n> On Mon, Feb 21, 2022 at 6:37 AM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>> On Mon, Feb 21 2022, Johannes Schindelin wrote:\n>>\n>> [I sent out an empty reply to this earlier by mistake, sorry about that]\n>>\n>> > [...]\n>> > Which brings me to the next concern: since `merge-tree` is a low-level\n>> > tool meant to be called by programs rather than humans, we need to make\n>> > sure that those messages remain machine-parseable, even if they contain\n>> > file names.\n>> >\n>> > Concretely: while I am not currently aware of any web UI that allows to\n>> > resolve simple rename/rename conflicts, it is easily conceivable how to\n>> > implement such a thing. When that happens, we will need to be able to\n>> > teach the server-side code to discern between the cases that can be\n>> > handled in the web UI (trivial merge conflicts, trivial rename/rename\n>> > conflicts) as compared to scenarios where the conflicts are just too\n>> > complex.\n>> >\n>> > Here's an excerpt from t4301:\n>> >\n>> > -- snip --\n>> > Auto-merging greeting\n>> > CONFLICT (content): Merge conflict in greeting\n>> > Auto-merging numbers\n>> > CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n>> > CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n>> > -- snap --\n>> >\n>> > This is the complete set of messages provided in the `test conflict\n>> > notices and such` test case.\n>> >\n>> > I immediately notice that every line contains at least one file name.\n>> > Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it\n>> > does not seem as if the file names are quoted:\n>> >\n>> >               path_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n>> >\n>> > (where `path` is used verbatim in a call to `merge_3way()` before that,\n>> > i.e. it must not have been quoted)\n>> >\n>> > I would like to register a wish to ensure that file names with special\n>> > characters (such as most notably line-feed characters) are quoted in these\n>> > messages, so that a simple server-side parser can handle messages starting\n>> > with `Auto-merging` and with `CONFLICT (content): Merge conflict in `, and\n>> > \"throw the hands up in the air\" if any other message prefix is seen.\n>> >\n>> > Do you think we can switch to `sq_quote_buf_pretty()` for these messages?\n>> > For the `Auto-merging` one, it would be trivial, but I fear that we will\n>> > have to work a bit on the `path_msg()` function\n>> > (https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it\n>> > accepts a variable list of arguments without any clue whether the\n>> > arguments refer to paths or not. (And I would be loathe to switch _all_\n>> > callers to do the quoting themselves.)\n>> >\n>> > I see 28 calls to that function, and at least a couple that pass not only\n>> > a path but also an OID (e.g.\n>> > https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).\n>> >\n>> > We could of course be sloppy and pass even OIDs through\n>> > `sq_quote_buf_pretty()` in `path_msg()`, knowing that there won't be any\n>> > special characters in them, but it gets more complicated e.g. in\n>> > https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we\n>> > pass an `strbuf` that contains a somewhat free-form commit message.\n>> >\n>> > I guess we could still pass those through `sq_quote_buf_pretty()`, even if\n>> > they are not paths, to ensure that there are no special characters in the\n>> > machine-parseable lines.\n>> >\n>> > What do you think?\n>>\n>> That sounds like a rather nasty hack, this is too, but demonstrates that\n>> we can pretty easily extract this in a machine-readable format with just\n>> a few lines now):\n>>\n>> diff --git a/merge-ort.c b/merge-ort.c\n>> index 8a5f201d190..a906881f9b3 100644\n>> --- a/merge-ort.c\n>> +++ b/merge-ort.c\n>> @@ -633,7 +633,7 @@ static void path_msg(struct merge_options *opt,\n>>                      int omittable_hint, /* skippable under --remerge-diff */\n>>                      const char *fmt, ...)\n>>  {\n>> -       va_list ap;\n>> +       va_list ap, cp;\n>>         struct strbuf *sb, *dest;\n>>         struct strbuf tmp = STRBUF_INIT;\n>>\n>> @@ -650,7 +650,9 @@ static void path_msg(struct merge_options *opt,\n>>\n>>         dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);\n>>\n>> +       va_copy(cp, ap);\n>>         va_start(ap, fmt);\n>> +\n>>         if (opt->priv->call_depth) {\n>>                 strbuf_addchars(dest, ' ', 2);\n>>                 strbuf_addstr(dest, \"From inner merge:\");\n>> @@ -659,6 +661,15 @@ static void path_msg(struct merge_options *opt,\n>>         strbuf_vaddf(dest, fmt, ap);\n>>         va_end(ap);\n>>\n>> +       va_start(cp, fmt);\n>> +       trace2_region_enter_printf(\"merge\", \"conflict/path\", opt->repo, \"%s\", path);\n>> +       trace2_region_leave(\"merge\", \"conflict/path\", opt->repo);\n>> +       trace2_region_enter_printf(\"merge\", \"conflict/fmt\", opt->repo, \"%s\", fmt);\n>> +       trace2_region_leave(\"merge\", \"conflict/fmt\", opt->repo);\n>> +       trace2_region_enter_printf_va(\"merge\", \"conflict/msg\", opt->repo, fmt, cp);\n>> +       trace2_region_leave(\"merge\", \"conflict/msg\", opt->repo);\n>> +       va_end(cp);\n>> +\n>>         if (opt->record_conflict_msgs_as_headers) {\n>>                 int i_sb = 0, i_tmp = 0;\n>>\n>> You can run that with one of the tests added in this series to get the\n>> output as JSON, e.g.:\n>>\n>>      GIT_TRACE2_EVENT=/dev/stderr GIT_TRACE2_EVENT_NESTING=10 ~/g/git/git merge-tree --write-tree --no-messages --name-only --messages side1 side2 2>&1|jq -r .| grep '\"msg\"'\n>>       \"msg\": \"whatever~side1\"\n>>       \"msg\": \"CONFLICT (file/directory): directory in the way of %s from %s; moving it to %s instead.\"\n>>       \"msg\": \"CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\"\n>>       \"msg\": \"whatever~side1\"\n>>       \"msg\": \"CONFLICT (modify/delete): %s deleted in %s and modified in %s.  Version %s of %s left in tree.\"\n>>       \"msg\": \"CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\"\n>>       \"msg\": \"numbers\"\n>>       \"msg\": \"Auto-merging %s\"\n>>       \"msg\": \"Auto-merging numbers\"\n>>       \"msg\": \"greeting\"\n>>       \"msg\": \"Auto-merging %s\"\n>>       \"msg\": \"Auto-merging greeting\"\n>>       \"msg\": \"greeting\"\n>>       \"msg\": \"CONFLICT (%s): Merge conflict in %s\"\n>>       \"msg\": \"CONFLICT (content): Merge conflict in greeting\"\n>>\n>> A \"proper\" fix for this doesn't sound too hard, we'd just instrument the\n>> path_msg() function to pass along some \"message category\", see\n>> e.g. unpack_plumbing_errors in unpack-trees.c for one example of such a\n>> thing, or the \"enum fsck_msg_id\".\n>>\n>> Then we'd just allow you to emit any of the sprintf() format itself, or\n>> the expanded version, the path, or an id like \"CONFLICT:file/directory\"\n>> or \"auto-merging\" etc.\n>\n> I don't see how this helps solve the problem Dscho was bringing up at\n> all.  Your reference to \"the path\" means you've missed his whole\n> complaint -- that with more complex conflicts (renames, directory/file\n> conflicts resolved via moving the file out of the way, mode conflicts\n> resolved by moving both files out of the way, etc) there are multiple\n> paths involved and he's trying to determine what those paths are.\n> He's particularly focusing on rename/rename cases where a single path\n> was renamed differently by the two sides of history (which results in\n> a conflict message only being associated with the path from the merge\n> base in order to avoid repeating the same message 2-3 times, but that\n> one message has three distinct paths embedded in the string).\n>\n> Also, the additional paths is not part of the API to path_msg; it's\n> merely embedded in a string.  (And, in case it bears repeating: as\n> mentioned elsewhere, we cannot assume there will only be one\n> path_msg() call per path, and we at least currently can't assume that\n> each path_msg() call is for a separate logical conflict; there might\n> be two for a single \"conflict\".)\n>\n> I agree that parsing these meant-for-human-consumption (and not\n> promised to be stable) messages is not a good way to go, but\n> pretending the current API has enough info to answer his questions\n> isn't right either.\n\nThe intent here wasn't to present a complete solution, but to reply to\nthe part of Johannes's E-Mail that e.g. mention \"and I would be loathe\nto switch _all_ callers to do the quoting themselves.\".\n\nI.e. it's a POC for passing this data further up the stack. The issue\nyou mention with the renaming case could/should be handled by having\nwhatever handles the vargs accept those N arguments, the POC doesn't\nhandle it.\n\nBut in any case, needing to convert \"28 calls to [path_msg()]\" doesn't\nseem like it's required.\n\nBut obviously we wouldn't want to use trace2 as a plumbing layer for\nmessage passing, but could format the same data in a similar way,\nespecially in the context of a discussion about filenames with odd\ncharacters in them (some of which JSON is inherently incapable of\nencoding).\n\n>> I think that would be particularly useful in conjuction with the\n>> --format changes I was proposing for this (and hacked up a WIP patch\n>> for). You could just have a similar --format-messages or whatever.\n>>\n>> Then you could pick \\0\\0 as a delimiter for your \"main\" --format, and\n>> \"\\0\" as the delimiter for your --format-messages, and thus be able to\n>> parse N-nested \\0-delimited content.\n>\n> To be honest, the --format stuff is sounding a little bit like a\n> solution in search of a problem.\n\nOpinions on this obviously differ, and I'm not going to pick this as my\nparticular hill to die on :)\n\nBut I do think it's the other way around, in that hardcoded output\nformats are a problem requiring solutions.\n\nWhich e.g. is seen (to be fair, in rather small inconveniences) in this\nseries where you need to grep out the first OID line etc. in the tests,\ninstead of the command just being able to be asked for the exact thing\nthe user wants.\n\nNow, perfect shouldn't be the enemy of the good, and I think this series\nis definitely in good enough shape as it is. But as\ne.g. \"builtin/ls-tree.c\" in \"seen\" currently shows using the\nstrbuf_expand() callback mechanism to emit output will result in less\ncode/special cases.\n\nNow, in this case the problem is mostly orthagonal, i.e. that the\nmerge-ort.c API understandably isn't returning structured data in this\ncase, as the goal so far has been replacing the in-tree\nmerge-recursive.c use-case.\n\nBut if & when that happens I think e.g. returning those as a \"struct\nstring_list\" with some custom struct in the \"util\" member might be a\ngood idea, followed by a strbuf_expand() knowing how to format fields\nfrom that struct.\n\nThe whole thing could then become e.g. (whitespace added for\nconvenience):\n\n\t--format=\"\\\n\t\t%(objectname)\n\t\t\\0\\0\n\t\t%(conflict:%(\n\t\t\t%(conflictmode)\n                        \\0\n                        %(conflictobject)\n                        \\0\n                        %(conflictstage)\n                        \\0\n                        %(conflictfilename)\n\t\t)\n\t\"\n\nAnd it seems to me like the whole of --show-messages etc. could be\ngeneralized as some (the \"if\" syntax would need to be extracted and\ngeneralized from ref-filter.c:\n\n    %(if:notequal=0)%(mergeresult)%(then)...%(end)\n\nSo again, I'm not saying it's needed now, just that I think the pattern\nof preparing structured data and throwing it at a formatting engine is\nworth considering sooner than later.\n\nIt we didn't have that for \"git-for-each-ref\" I think it would have N\nnumber of \"--only-this-but-not-that\"-type options, which this new\ncommand already requires.\n\nAnd with a formatting engine things like \"I want my output quoted for\nconsumption by programming language X\" become much harder, but which\n\"git-for-each-ref\" already supports using that formatting mechanism.\n"},{"id":"449852","messageId":"CABPp-BG25_TutatgNmK6vgq3akxpYHQ8QBnz-65_F_3oCA1nJA@mail.gmail.com","threadId":"57288","inReplyTo":"220228.86sfs35ojn.gmgdl@evledraar.gmail.com","subject":"Re: machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-03-01T03:49:20Z","receivedAt":"2022-03-01T03:49:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Feb 28, 2022 at 1:27 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Tue, Feb 22 2022, Elijah Newren wrote:\n>\n[...]\n> > I don't see how this helps solve the problem Dscho was bringing up at\n> > all.  Your reference to \"the path\" means you've missed his whole\n> > complaint -- that with more complex conflicts (renames, directory/file\n> > conflicts resolved via moving the file out of the way, mode conflicts\n> > resolved by moving both files out of the way, etc) there are multiple\n> > paths involved and he's trying to determine what those paths are.\n> > He's particularly focusing on rename/rename cases where a single path\n> > was renamed differently by the two sides of history (which results in\n> > a conflict message only being associated with the path from the merge\n> > base in order to avoid repeating the same message 2-3 times, but that\n> > one message has three distinct paths embedded in the string).\n> >\n> > Also, the additional paths is not part of the API to path_msg; it's\n> > merely embedded in a string.  (And, in case it bears repeating: as\n> > mentioned elsewhere, we cannot assume there will only be one\n> > path_msg() call per path, and we at least currently can't assume that\n> > each path_msg() call is for a separate logical conflict; there might\n> > be two for a single \"conflict\".)\n> >\n> > I agree that parsing these meant-for-human-consumption (and not\n> > promised to be stable) messages is not a good way to go, but\n> > pretending the current API has enough info to answer his questions\n> > isn't right either.\n>\n> The intent here wasn't to present a complete solution, but to reply to\n> the part of Johannes's E-Mail that e.g. mention \"and I would be loathe\n> to switch _all_ callers to do the quoting themselves.\".\n>\n> I.e. it's a POC for passing this data further up the stack. The issue\n> you mention with the renaming case could/should be handled by having\n> whatever handles the vargs accept those N arguments, the POC doesn't\n> handle it.\n>\n> But in any case, needing to convert \"28 calls to [path_msg()]\" doesn't\n> seem like it's required.\n\nThe problem isn't that it's an incomplete solution, it's that AFAICT,\nthe user's stated problem is not aided at all by this POC.  Passing\nexisting data further up the stack cannot solve the problem if the\ndata being passed is insufficient for the problem at hand.\n\nPerhaps you have some clever solution to get the extra information,\nthough?  Could you elaborate on how path_msg() could handle its\nvarargs differently to deduce which of those correspond to paths?  The\nonly way I can see how to do that, short of modifying all 28 callers\nof path_msg() to pass those paths as additional information, is hacks\nlike parsing the (non-stable, not-meant-for-machine-parseability)\nformat string.\n\n(Getting the paths would get us most of the way to a solution, though\nit's still incomplete.  But it's the relevant bit under discussion\nhere.)\n\n> But obviously we wouldn't want to use trace2 as a plumbing layer for\n> message passing, but could format the same data in a similar way,\n> especially in the context of a discussion about filenames with odd\n> characters in them (some of which JSON is inherently incapable of\n> encoding).\n>\n> >> I think that would be particularly useful in conjuction with the\n> >> --format changes I was proposing for this (and hacked up a WIP patch\n> >> for). You could just have a similar --format-messages or whatever.\n> >>\n> >> Then you could pick \\0\\0 as a delimiter for your \"main\" --format, and\n> >> \"\\0\" as the delimiter for your --format-messages, and thus be able to\n> >> parse N-nested \\0-delimited content.\n> >\n> > To be honest, the --format stuff is sounding a little bit like a\n> > solution in search of a problem.\n>\n> Opinions on this obviously differ, and I'm not going to pick this as my\n> particular hill to die on :)\n>\n> But I do think it's the other way around, in that hardcoded output\n> formats are a problem requiring solutions.\n\nI might be more convinced if folks tried to address how to output\nthings _after_ we had determined *what* things should be output.  If\nwe don't have sufficient information to solve what users want,\ndiscussing how we format the information we do have cannot help solve\nthe actual problems.  It might be useful as an add-on later, but\ndiscussing it first is putting the cart before the horse.\n"},{"id":"450607","messageId":"nycvar.QRO.7.76.6.2203071718090.11118@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BGnqXdFBNAyKRXgvCHv+aUZTMg-CgcQf95dKAR-e1zSjQ@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-03-07T16:27:48Z","receivedAt":"2022-03-07T16:28:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Fri, 25 Feb 2022, Elijah Newren wrote:\n\n> On Fri, Feb 25, 2022 at 8:31 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Tue, 22 Feb 2022, Elijah Newren wrote:\n> >\n> > > On Mon, Feb 21, 2022 at 2:46 AM Johannes Schindelin\n> > > <Johannes.Schindelin@gmx.de> wrote:\n> > > >\n> > > > Concretely: while I am not currently aware of any web UI that allows\n> > > > to resolve simple rename/rename conflicts, it is easily conceivable\n> > > > how to implement such a thing. When that happens, we will need to be\n> > > > able to teach the server-side code to discern between the cases that\n> > > > can be handled in the web UI (trivial merge conflicts, trivial\n> > > > rename/rename conflicts) as compared to scenarios where the conflicts\n> > > > are just too complex.\n> > >\n> > > Um, I'm really worried about attempting to make the conflict notices\n> > > machine parseable.  I don't like that idea at all, and I even tried to\n> > > rule that out already with my wording:\n> > > \"\"\"\n> > > In all cases, the\n> > > <Informational messages> section has the necessary info, though it is\n> > > not designed to be machine parseable.\n> > > \"\"\"\n> > > though maybe I should have been even more explicit.  The restrictions\n> > > that those messages be stable is too rigid, I think.  I also think\n> > > they're a poor way to communicate information to a higher level tool.\n> > > I would much rather us add some kind of additional return data\n> > > structures from merge ort and use them if we want extra info.\n> >\n> > Okay.\n> >\n> > I thought that we could keep the `CONFLICT (<type>)` constant enough to\n> > serve as such a machine-parseable thing.\n>\n> That _probably_ is, but I thought you wanted to parse all N paths\n> embedded in the message after that part as well?\n\nActually, no, sorry for being unclear. My main aim with the\nmachine-parseable messages was to detect whether a given failed merge\ncontains _only_ conflicts of the sort that a particular UI can handle.\n\nIn that respect, I would not even need to parse the file names, I'd just\nrequire them not to contain line feed characters ;-)\n\n> > And then presenting\n> > `<path>NUL<message>NUL` could have served my use case well...\n>\n> Would it?  Wouldn't you need something more like\n>\n> <number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL\n>  ?\n\nProbably, you're right.\n\n> I mean, if rename/rename is what you want to handle, there are three\n> paths in that message.  And you need to know all three paths in order\n> to combine the relevant parts of the <Conflicted File Info> section\n> together.\n>\n> (Also, while we're at it, I decided to throw a stable short-type\n> description string (e.g. \"CONFLICT (rename/rename)\") in there, which\n> will _probably_ be the first part of the message string but still\n> allow us to change the message string later if we want.)\n\nYes, I very much like that idea to keep the prefix in a format that we can\nguarantee to be stable enough for applications (or server backends) to\nrely on.\n\n> Also, we'd want those parsing this information to keep in mind that:\n>   * Any given conflict can affect multiple paths\n>   * Any path can be part of multiple conflicts\n>   * (The above two items imply a potentially many-to-many relationship\n> between paths and conflicts)\n>   * Paths listed in these logical conflicts may not correspond to a\n> file in the index (they could be a directory, or file that was in a\n> previous version)\n>   * Some of these \"logical conflicts\" are not actually conflicts but\n> just notices (e.g. \"auto-merging\" or \"submodule updated\" or \"WARNING\"\n> or \"<submodules are weird>\" messages)\n>\n> and we'd have to do some work to make sure the paths in the given\n> messages lined up with the files actually recorded in the index (e.g.\n> with distinct types we rename both files to avoid the collision, but\n> print the conflict notice for the original path rather than the new\n> paths)\n>\n> [...]\n> > > But I'm going to reserve the right in merge-ort to modify, add, or\n> > > delete any of those messages passed to path_msg(), which might wreak\n> > > havoc on your attempts to parse those strings.  I think they're a bad\n> > > form for communicating information to a script or program, and trying\n> > > to transform them into such risks making them suboptimal at\n> > > communicating info to humans.  These messages should optimize the\n> > > latter, and if we want something for the former, it should probably be\n> > > a new independent bit of info.\n> >\n> > Makes sense.\n> >\n> > So we need something in addition to those messages.\n>\n> Yes.  Does the proposal above sound like it'd cover your needs?  If\n> so, we'd probably need to go through all the callers to path_msg() and\n> either add an immediate call to another function immediately\n> afterwards that stores this additional information or somehow change\n> the path_msg() call itself to somehow take an additional arbitrary\n> list of arguments representing the paths and short-desc we want to\n> store somewhere.\n\nYes, you're right, the proper thing to do is to go through the callers to\n`path_msg()` so that we can provide that stable output you proposed. A\ncouple of thoughts about this:\n\n* These are not informal messages, i.e. I think we would need another flag\n  that would then trigger another double-`NUL`-separated section to be\n  shown, probably between the conflicted file info and the informational\n  messages.\n\n* Instead of _adding_ the calls, we could look into refactoring the\n  existing `path_msg()` calls, introducing yet another function that would\n  call `path_msg()` but also optionally generate that machine-parseable\n  conflict info.\n\n* None of this needs to hold up `en/merge-tree`. I am sorry that I am the\n  blocker (unintentionally so, I guarantee you that!).\n\nCiao,\nDscho\n"},{"id":"450715","messageId":"CABPp-BGW39_5r8Lbt3ymR+F_=hWJcf=2e7O75vFNJ=3CEL5s=g@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2203071718090.11118@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-03-08T08:25:16Z","receivedAt":"2022-03-08T08:25:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Mar 7, 2022 at 8:27 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Fri, 25 Feb 2022, Elijah Newren wrote:\n>\n> > On Fri, Feb 25, 2022 at 8:31 AM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> > >\n> > > On Tue, 22 Feb 2022, Elijah Newren wrote:\n> > >\n> > > > On Mon, Feb 21, 2022 at 2:46 AM Johannes Schindelin\n> > > > <Johannes.Schindelin@gmx.de> wrote:\n> > > > >\n> > > > > Concretely: while I am not currently aware of any web UI that allows\n> > > > > to resolve simple rename/rename conflicts, it is easily conceivable\n> > > > > how to implement such a thing. When that happens, we will need to be\n> > > > > able to teach the server-side code to discern between the cases that\n> > > > > can be handled in the web UI (trivial merge conflicts, trivial\n> > > > > rename/rename conflicts) as compared to scenarios where the conflicts\n> > > > > are just too complex.\n> > > >\n> > > > Um, I'm really worried about attempting to make the conflict notices\n> > > > machine parseable.  I don't like that idea at all, and I even tried to\n> > > > rule that out already with my wording:\n> > > > \"\"\"\n> > > > In all cases, the\n> > > > <Informational messages> section has the necessary info, though it is\n> > > > not designed to be machine parseable.\n> > > > \"\"\"\n> > > > though maybe I should have been even more explicit.  The restrictions\n> > > > that those messages be stable is too rigid, I think.  I also think\n> > > > they're a poor way to communicate information to a higher level tool.\n> > > > I would much rather us add some kind of additional return data\n> > > > structures from merge ort and use them if we want extra info.\n> > >\n> > > Okay.\n> > >\n> > > I thought that we could keep the `CONFLICT (<type>)` constant enough to\n> > > serve as such a machine-parseable thing.\n> >\n> > That _probably_ is, but I thought you wanted to parse all N paths\n> > embedded in the message after that part as well?\n>\n> Actually, no, sorry for being unclear. My main aim with the\n> machine-parseable messages was to detect whether a given failed merge\n> contains _only_ conflicts of the sort that a particular UI can handle.\n\n...and in order to do that, you'd need to parse all the filenames to\ndetermine whether files were involved in multiple conflict types,\nsince you earlier suggested you would just bail on such conflicts (at\nhttps://lore.kernel.org/git/nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet/).\nIn particular, I think the \"mod6\" and \"rrdd\" and \"rad\" cases kind of\ntriggered those comments, and rename/rename is involved in two of\nthose.\n\nFurther, even if you could assume there'd only be simple conflicts\nsuch as the simple rename/rename case, you'd still need the files so\nthat you know _which_ of the modes/oids in the <Conflicted file info>\nsection relate to the message you are looking at.  (As mentioned\npreviously, sorting the <Conflicted file info> to attempt to put them\ntogether is not feasible and is a no-go.)\n\n> > > And then presenting\n> > > `<path>NUL<message>NUL` could have served my use case well...\n> >\n> > Would it?  Wouldn't you need something more like\n> >\n> > <number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL\n> >  ?\n>\n> Probably, you're right.\n>\n> > I mean, if rename/rename is what you want to handle, there are three\n> > paths in that message.  And you need to know all three paths in order\n> > to combine the relevant parts of the <Conflicted File Info> section\n> > together.\n> >\n> > (Also, while we're at it, I decided to throw a stable short-type\n> > description string (e.g. \"CONFLICT (rename/rename)\") in there, which\n> > will _probably_ be the first part of the message string but still\n> > allow us to change the message string later if we want.)\n>\n> Yes, I very much like that idea to keep the prefix in a format that we can\n> guarantee to be stable enough for applications (or server backends) to\n> rely on.\n\nUm, I explicitly avoided saying the prefix would be stable by\nintroducing a separate short string just so that we could change the\nprefix later if wanted.  The short string is _probably_ the current\nprefix or something close to it, but that stable string wouldn't\nnecessarily remain the prefix of the message string, since the entire\nmessage string would be allowed to change.\n\n> > Also, we'd want those parsing this information to keep in mind that:\n> >   * Any given conflict can affect multiple paths\n> >   * Any path can be part of multiple conflicts\n> >   * (The above two items imply a potentially many-to-many relationship\n> > between paths and conflicts)\n> >   * Paths listed in these logical conflicts may not correspond to a\n> > file in the index (they could be a directory, or file that was in a\n> > previous version)\n> >   * Some of these \"logical conflicts\" are not actually conflicts but\n> > just notices (e.g. \"auto-merging\" or \"submodule updated\" or \"WARNING\"\n> > or \"<submodules are weird>\" messages)\n> >\n> > and we'd have to do some work to make sure the paths in the given\n> > messages lined up with the files actually recorded in the index (e.g.\n> > with distinct types we rename both files to avoid the collision, but\n> > print the conflict notice for the original path rather than the new\n> > paths)\n> >\n> > [...]\n> > > > But I'm going to reserve the right in merge-ort to modify, add, or\n> > > > delete any of those messages passed to path_msg(), which might wreak\n> > > > havoc on your attempts to parse those strings.  I think they're a bad\n> > > > form for communicating information to a script or program, and trying\n> > > > to transform them into such risks making them suboptimal at\n> > > > communicating info to humans.  These messages should optimize the\n> > > > latter, and if we want something for the former, it should probably be\n> > > > a new independent bit of info.\n> > >\n> > > Makes sense.\n> > >\n> > > So we need something in addition to those messages.\n> >\n> > Yes.  Does the proposal above sound like it'd cover your needs?  If\n> > so, we'd probably need to go through all the callers to path_msg() and\n> > either add an immediate call to another function immediately\n> > afterwards that stores this additional information or somehow change\n> > the path_msg() call itself to somehow take an additional arbitrary\n> > list of arguments representing the paths and short-desc we want to\n> > store somewhere.\n>\n> Yes, you're right, the proper thing to do is to go through the callers to\n> `path_msg()` so that we can provide that stable output you proposed. A\n> couple of thoughts about this:\n>\n> * These are not informal messages, i.e. I think we would need another flag\n>   that would then trigger another double-`NUL`-separated section to be\n>   shown, probably between the conflicted file info and the informational\n>   messages.\n\nWhy would that be a separate section, though?  While we don't want\nmachines parsing the informal messages and trying to determine the\ncomponents of that message, they definitely should be able to tell\nwhich informal messages are associated with which paths (otherwise how\ncan you group the <Conflict message info> as per your needs and how do\nyou know which message to show the user when dealing with that\nparticular conflict?).  That requirement suggests the informal\nmessages should be part of the same section.  Thus my suggestion to\njust make it be the <message> part at the end of my earlier suggestion\nof\n\n<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL\n\n> * Instead of _adding_ the calls, we could look into refactoring the\n>   existing `path_msg()` calls, introducing yet another function that would\n>   call `path_msg()` but also optionally generate that machine-parseable\n>   conflict info.\n\nSo of the two alternatives I suggested above, it sounds like you're in\nfavor of the \"or somehow change the path_msg() call itself to somehow\ntake an additional arbitrary list of arguments representing the paths\nand short-desc we want to store somewhere\" option I suggested.  I\nthink I prefer that one too.\n\n> * None of this needs to hold up `en/merge-tree`. I am sorry that I am the\n>   blocker (unintentionally so, I guarantee you that!).\n\nSometimes I find it frustrating that changes don't merge down for\nmonths, particularly when the topic was already finished weeks and\nweeks (if not months) ago and/or when there's a follow-on series\ndepending on it.  But in this case I have no follow-on series ready to\nsend, and this topic has been under (very!) active discussion\nessentially the whole time.  And, perhaps even more importantly, I'd\nlike to avoid solidifying the \"machine parseable output format\"\nprematurely and making it harder to work around later.  I think\naddressing this issue you bring up requires fundamentally redoing the\ninformational messages section as per my proposal, and it makes me\nwonder what should even be shown without the -z option (thoughts\nwelcome).\n\nSo, this is one series where even if everyone else says to merge it\nalready, I'd like to wait a bit longer on it until I feel confident we\nhave a solution that handles at least the current usecases.\n"},{"id":"451029","messageId":"nycvar.QRO.7.76.6.2203101546110.357@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BGW39_5r8Lbt3ymR+F_=hWJcf=2e7O75vFNJ=3CEL5s=g@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-03-10T15:10:49Z","receivedAt":"2022-03-10T15:13:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Tue, 8 Mar 2022, Elijah Newren wrote:\n\n> On Mon, Mar 7, 2022 at 8:27 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Fri, 25 Feb 2022, Elijah Newren wrote:\n> >\n> > > On Fri, Feb 25, 2022 at 8:31 AM Johannes Schindelin\n> > > <Johannes.Schindelin@gmx.de> wrote:\n> > > >\n> > > > On Tue, 22 Feb 2022, Elijah Newren wrote:\n> > > >\n> > > > > On Mon, Feb 21, 2022 at 2:46 AM Johannes Schindelin\n> > > > > <Johannes.Schindelin@gmx.de> wrote:\n> > > > > >\n> > > > > > Concretely: while I am not currently aware of any web UI that\n> > > > > > allows to resolve simple rename/rename conflicts, it is easily\n> > > > > > conceivable how to implement such a thing. When that happens,\n> > > > > > we will need to be able to teach the server-side code to\n> > > > > > discern between the cases that can be handled in the web UI\n> > > > > > (trivial merge conflicts, trivial rename/rename conflicts) as\n> > > > > > compared to scenarios where the conflicts are just too\n> > > > > > complex.\n> > > > >\n> > > > > Um, I'm really worried about attempting to make the conflict\n> > > > > notices machine parseable.  I don't like that idea at all, and I\n> > > > > even tried to rule that out already with my wording: \"\"\" In all\n> > > > > cases, the <Informational messages> section has the necessary\n> > > > > info, though it is not designed to be machine parseable. \"\"\"\n> > > > > though maybe I should have been even more explicit.  The\n> > > > > restrictions that those messages be stable is too rigid, I\n> > > > > think.  I also think they're a poor way to communicate\n> > > > > information to a higher level tool. I would much rather us add\n> > > > > some kind of additional return data structures from merge ort\n> > > > > and use them if we want extra info.\n> > > >\n> > > > Okay.\n> > > >\n> > > > I thought that we could keep the `CONFLICT (<type>)` constant\n> > > > enough to serve as such a machine-parseable thing.\n> > >\n> > > That _probably_ is, but I thought you wanted to parse all N paths\n> > > embedded in the message after that part as well?\n> >\n> > Actually, no, sorry for being unclear. My main aim with the\n> > machine-parseable messages was to detect whether a given failed merge\n> > contains _only_ conflicts of the sort that a particular UI can handle.\n>\n> ...and in order to do that, you'd need to parse all the filenames to\n> determine whether files were involved in multiple conflict types, since\n> you earlier suggested you would just bail on such conflicts (at\n> https://lore.kernel.org/git/nycvar.QRO.7.76.6.2202211059430.26495@tvgsbejvaqbjf.bet/).\n\nThat's a good point. I had somehow still in my mind that the `CONFLICT\n(<type>)` prefix would already give me enough data to decide whether to\nbail out or not, but that prefix would only tell me about a single\n`rename/rename` conflict, not about multiple such conflicts involving the\n_same_ file.\n\n> In particular, I think the \"mod6\" and \"rrdd\" and \"rad\" cases kind of\n> triggered those comments, and rename/rename is involved in two of those.\n>\n> Further, even if you could assume there'd only be simple conflicts such\n> as the simple rename/rename case, you'd still need the files so that you\n> know _which_ of the modes/oids in the <Conflicted file info> section\n> relate to the message you are looking at.  (As mentioned previously,\n> sorting the <Conflicted file info> to attempt to put them together is\n> not feasible and is a no-go.)\n\nTrue.\n\n> > > > And then presenting `<path>NUL<message>NUL` could have served my\n> > > > use case well...\n> > >\n> > > Would it?  Wouldn't you need something more like\n> > >\n> > > <number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL\n> > > ?\n> >\n> > Probably, you're right.\n> >\n> > > I mean, if rename/rename is what you want to handle, there are three\n> > > paths in that message.  And you need to know all three paths in\n> > > order to combine the relevant parts of the <Conflicted File Info>\n> > > section together.\n> > >\n> > > (Also, while we're at it, I decided to throw a stable short-type\n> > > description string (e.g. \"CONFLICT (rename/rename)\") in there, which\n> > > will _probably_ be the first part of the message string but still\n> > > allow us to change the message string later if we want.)\n> >\n> > Yes, I very much like that idea to keep the prefix in a format that we\n> > can guarantee to be stable enough for applications (or server\n> > backends) to rely on.\n>\n> Um, I explicitly avoided saying the prefix would be stable by\n> introducing a separate short string just so that we could change the\n> prefix later if wanted.  The short string is _probably_ the current\n> prefix or something close to it, but that stable string wouldn't\n> necessarily remain the prefix of the message string, since the entire\n> message string would be allowed to change.\n\nOkay.\n\n> > [T]he proper thing to do is to go through the callers to `path_msg()`\n> > so that we can provide that stable output you proposed. A couple of\n> > thoughts about this:\n> >\n> > * These are not informal messages, i.e. I think we would need another flag\n> >   that would then trigger another double-`NUL`-separated section to be\n> >   shown, probably between the conflicted file info and the informational\n> >   messages.\n>\n> Why would that be a separate section, though?\n\nI thought you wanted to keep the informal messages human-readable, so\nnaturally I thought that the machine-readable part should be in a separate\nsection.\n\n> While we don't want machines parsing the informal messages and trying to\n> determine the components of that message, they definitely should be able\n> to tell which informal messages are associated with which paths\n> (otherwise how can you group the <Conflict message info> as per your\n> needs and how do you know which message to show the user when dealing\n> with that particular conflict?).  That requirement suggests the informal\n> messages should be part of the same section.  Thus my suggestion to just\n> make it be the <message> part at the end of my earlier suggestion of\n>\n> <number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL\n\nOh, okay.\n\nShould this format be the one with `-z`, and the current format remain the\none being shown without `-z`?\n\n> > * Instead of _adding_ the calls, we could look into refactoring the\n> >   existing `path_msg()` calls, introducing yet another function that would\n> >   call `path_msg()` but also optionally generate that machine-parseable\n> >   conflict info.\n>\n> So of the two alternatives I suggested above, it sounds like you're in\n> favor of the \"or somehow change the path_msg() call itself to somehow\n> take an additional arbitrary list of arguments representing the paths\n> and short-desc we want to store somewhere\" option I suggested.  I\n> think I prefer that one too.\n\nYes!\n\n> > * None of this needs to hold up `en/merge-tree`. I am sorry that I am the\n> >   blocker (unintentionally so, I guarantee you that!).\n>\n> Sometimes I find it frustrating that changes don't merge down for\n> months, particularly when the topic was already finished weeks and\n> weeks (if not months) ago and/or when there's a follow-on series\n> depending on it.  But in this case I have no follow-on series ready to\n> send, and this topic has been under (very!) active discussion\n> essentially the whole time.  And, perhaps even more importantly, I'd\n> like to avoid solidifying the \"machine parseable output format\"\n> prematurely and making it harder to work around later.  I think\n> addressing this issue you bring up requires fundamentally redoing the\n> informational messages section as per my proposal, and it makes me\n> wonder what should even be shown without the -z option (thoughts\n> welcome).\n\nMy suggestion is above: without `-z`, the messages should be identical to\nwhat is shown now.\n\n> So, this is one series where even if everyone else says to merge it\n> already, I'd like to wait a bit longer on it until I feel confident we\n> have a solution that handles at least the current usecases.\n\nFair enough, you're in charge of this series, and I really like what you\ncame up with.\n\nMy thinking was driven more by the users' side, as I am relatively eager\nto integrate this into production, but am loathe to do that with an early\niteration of `en/merge-tree` that might be substantially revamped, still.\n\nIt can bog me down quite well if I have to adjust local (or not-so-local)\npatch series to refactorings in core Git. For example, the other day, I\nwas blocked from more interesting work until I could resolve all kinds of\nconflicts where core Git renaming `hash_object_file_literally()` to\n`write_object_file_literally()` completely intersected with\nactually-interesting `unsigned long` to `size_t` changes in Git for\nWindows. And let me tell you, once you have to deal with such conflicts a\ncouple of times, being prevented from doing work that is actually fun, it\nis relatively easy to become frustrated and think about those refactorings\nas pointless and as useless churn, no matter how well-intended they may\nbe.\n\nSo my prodding is kind of selfish, as an `en/merge-tree` cherry-picked\nfrom `next` would provide somewhat of a promise that I do _not_ have to\nspend _so_ much time adjusting my code in case a new iteration with\nsubstantial changes shows up ;-)\n\nBut you're right, let's tread wisely and only call this patch series done\nonly when we are reasonably certain that the machine-parseable output is\nstable.\n\nSpeaking of which: Would you like me to give that `path_msg()` refactoring\na try, or are you already busy with it? (I cannot make any promises, but I\nalso do not want to lean on you, to leave you alone with that work, I\nwould feel really bad about that.)\n\nCiao,\nDscho\n"},{"id":"455260","messageId":"nycvar.QRO.7.76.6.2205131220200.352@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2203101546110.357@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-05-13T10:21:33Z","receivedAt":"2022-05-13T10:22:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Thu, 10 Mar 2022, Johannes Schindelin wrote:\n\n> On Tue, 8 Mar 2022, Elijah Newren wrote:\n>\n> > So, this is one series where even if everyone else says to merge it\n> > already, I'd like to wait a bit longer on it until I feel confident we\n> > have a solution that handles at least the current usecases.\n>\n> Fair enough, you're in charge of this series, and I really like what you\n> came up with.\n>\n> My thinking was driven more by the users' side, as I am relatively eager\n> to integrate this into production, but am loathe to do that with an early\n> iteration of `en/merge-tree` that might be substantially revamped, still.\n\nI've been bogged down with things elsewhere, but should now have time to\nhelp on this end.\n\nElijah, _is_ there anything I can help with?\n\nThanks,\nDscho\n"},{"id":"455376","messageId":"CABPp-BHQPrun3xhXBhbBnZ9cAy1sV7_r-kGsQhC-YsRMvoERmw@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2205131220200.352@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-05-17T08:23:40Z","receivedAt":"2022-05-17T08:23:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Johannes,\n\nOn Fri, May 13, 2022 at 3:21 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> On Thu, 10 Mar 2022, Johannes Schindelin wrote:\n>\n> > On Tue, 8 Mar 2022, Elijah Newren wrote:\n> >\n> > > So, this is one series where even if everyone else says to merge it\n> > > already, I'd like to wait a bit longer on it until I feel confident we\n> > > have a solution that handles at least the current usecases.\n> >\n> > Fair enough, you're in charge of this series, and I really like what you\n> > came up with.\n> >\n> > My thinking was driven more by the users' side, as I am relatively eager\n> > to integrate this into production, but am loathe to do that with an early\n> > iteration of `en/merge-tree` that might be substantially revamped, still.\n>\n> I've been bogged down with things elsewhere, but should now have time to\n> help on this end.\n>\n> Elijah, _is_ there anything I can help with?\n\nYeah, I've been bogged down with other things too; the little Git time\nI've had has been spent responding to review requests or other things\nfolks manually were asking for my input on.\n\nI think I got a fair amount of this implemented about a month or so\nago.  I just pushed up what I have to the wip-for-in-core-merge-tree\nbranch of newren/git.  Some notes:\n\n  * A big \"WIP\" commit that needs to be broken up\n  * The previous \"output\" member of merge_result, containing a strmap\nof conflict and informational messages (basically a mapping of\nfilename -> strbuf) now needs to be replaced by a strmap \"conflicts\",\nwhich is now a mapping of primary_filename -> logical_conflicts, and\nlogical_conflicts is an array of logical_conflict, and\nlogical_conflict has a type, array of paths, and message.\n  * Since \"output\" is no longer part of merge_result, the new\nremerge-diff functionality is going to need to be modified since it\nused that field, and instead iterate on \"conflicts\" to get the same\ninformation\n  * I have some FIXME comments in a couple places where I need to\nfigure out how I want to pass the variable number of arguments (in a\nfunction already accepting a variable number of arguments for other\nreasons, making the function in a way have to variable length lists of\narguments)\n  * The new enums and structs I added to merge-ort.c really have to be\nadded to merge-ort.h and become part of the API.  Feels a little\nunfortunate since it'll make the API _much_ more involved, but I don't\nsee any other way to solve your usecase.\n\nIf you want to take a stab at the above, or even see if my changes\nmake sense (sorry for it all being squashed into one big commit and\nnot having good commit messages, but, you know...you did ask), that'd\nbe great.\n"},{"id":"456640","messageId":"nycvar.QRO.7.76.6.2206032359210.349@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BHQPrun3xhXBhbBnZ9cAy1sV7_r-kGsQhC-YsRMvoERmw@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-06-03T22:11:58Z","receivedAt":"2022-06-03T22:12:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Tue, 17 May 2022, Elijah Newren wrote:\n\n> On Fri, May 13, 2022 at 3:21 AM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > On Thu, 10 Mar 2022, Johannes Schindelin wrote:\n> >\n> > > On Tue, 8 Mar 2022, Elijah Newren wrote:\n> > >\n> > > > So, this is one series where even if everyone else says to merge it\n> > > > already, I'd like to wait a bit longer on it until I feel confident we\n> > > > have a solution that handles at least the current usecases.\n> > >\n> > > Fair enough, you're in charge of this series, and I really like what you\n> > > came up with.\n> > >\n> > > My thinking was driven more by the users' side, as I am relatively eager\n> > > to integrate this into production, but am loathe to do that with an early\n> > > iteration of `en/merge-tree` that might be substantially revamped, still.\n> >\n> > I've been bogged down with things elsewhere, but should now have time to\n> > help on this end.\n> >\n> > Elijah, _is_ there anything I can help with?\n>\n> Yeah, I've been bogged down with other things too; the little Git time\n> I've had has been spent responding to review requests or other things\n> folks manually were asking for my input on.\n>\n> I think I got a fair amount of this implemented about a month or so\n> ago.  I just pushed up what I have to the wip-for-in-core-merge-tree\n> branch of newren/git.\n\nThank you so much!\n\nI worked a few hours on this and pushed up my changes under the same\nbranch name to dscho/git.\n\n> Some notes:\n>\n>   * A big \"WIP\" commit that needs to be broken up\n\nI did not yet start on that.\n\n>   * The previous \"output\" member of merge_result, containing a strmap\n> of conflict and informational messages (basically a mapping of\n> filename -> strbuf) now needs to be replaced by a strmap \"conflicts\",\n> which is now a mapping of primary_filename -> logical_conflicts, and\n> logical_conflicts is an array of logical_conflict, and\n> logical_conflict has a type, array of paths, and message.\n>   * Since \"output\" is no longer part of merge_result, the new\n> remerge-diff functionality is going to need to be modified since it\n> used that field, and instead iterate on \"conflicts\" to get the same\n> information\n\nI punted on that for now, recreating an `output`-style strmap and storing\nit as `path_messages` attribute.\n\n>   * I have some FIXME comments in a couple places where I need to\n> figure out how I want to pass the variable number of arguments (in a\n> function already accepting a variable number of arguments for other\n> reasons, making the function in a way have to variable length lists of\n> arguments)\n\nIn my WIP fixups, I refactored this into a version that takes varargs and\nanother version that takes a string_list.\n\nHowever, after getting all this to compile and t4301 to pass, I think we\nactually only need a version that takes up to two \"other\" paths, and a\nversion that takes a string_list with those \"other\" paths, where the\nformer constructs a temporary string_list and then calls the latter.\n\n>   * The new enums and structs I added to merge-ort.c really have to be\n> added to merge-ort.h and become part of the API.  Feels a little\n> unfortunate since it'll make the API _much_ more involved, but I don't\n> see any other way to solve your usecase.\n\nI agree, but I did not do that yet ;-)\n\nAnother thing I noticed is that we can probably ensure consistency between\nthe `conflict_and_info_types` enum and the `type_short_descriptions` array\nby using the same C99 construct we're already using in the\n`advice_setting` array in advice.c:\n\n\tstatic const char *type_short_descriptions[NB_CONFLICT_TYPES] = {\n\t\t/*** \"Simple\" conflicts and informational messages ***/\n\t\t[INFO_AUTO_MERGING] = \"Auto-merging\",\n\t\t[CONFLICT_CONTENTS] = \"CONFLICT (contents)\",\n\t[...]\n\n> If you want to take a stab at the above, or even see if my changes\n> make sense (sorry for it all being squashed into one big commit and\n> not having good commit messages, but, you know...you did ask), that'd\n> be great.\n\nYes, I did ask, and I did receive ;-)\n\nThank you so much! It would be great if you could have a quick look over\nthe commits I added on top of your branch, to see whether things make more\nor less sense to you. But if you're too busy elsewhere, I am one of the\nbest persons to understand that, too.\n\nThanks!\nDscho\n"},{"id":"456700","messageId":"nycvar.QRO.7.76.6.2206051733040.349@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2206032359210.349@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-06-05T15:40:13Z","receivedAt":"2022-06-05T15:40:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Sat, 4 Jun 2022, Johannes Schindelin wrote:\n\n> On Tue, 17 May 2022, Elijah Newren wrote:\n>\n> > I think I got a fair amount of this implemented about a month or so\n> > ago.  I just pushed up what I have to the wip-for-in-core-merge-tree\n> > branch of newren/git.\n>\n> Thank you so much!\n>\n> I worked a few hours on this and pushed up my changes under the same\n> branch name to dscho/git.\n\nAnd I worked on it some more, and pushed up the result (which passes the\nCI build except for some problem caused by changes outside of Git's source\ncode) ;-)\n\n> > Some notes:\n> >\n> >   * A big \"WIP\" commit that needs to be broken up\n>\n> I did not yet start on that.\n\nStill no start on that yet.\n\n> >   * The previous \"output\" member of merge_result, containing a strmap\n> > of conflict and informational messages (basically a mapping of\n> > filename -> strbuf) now needs to be replaced by a strmap \"conflicts\",\n> > which is now a mapping of primary_filename -> logical_conflicts, and\n> > logical_conflicts is an array of logical_conflict, and\n> > logical_conflict has a type, array of paths, and message.\n> >   * Since \"output\" is no longer part of merge_result, the new\n> > remerge-diff functionality is going to need to be modified since it\n> > used that field, and instead iterate on \"conflicts\" to get the same\n> > information\n>\n> I punted on that for now, recreating an `output`-style strmap and storing\n> it as `path_messages` attribute.\n\nI still punted on that because I wanted to see whether I could address the\ntest suite failures first (narrator's voice: he could).\n\n> >   * I have some FIXME comments in a couple places where I need to\n> > figure out how I want to pass the variable number of arguments (in a\n> > function already accepting a variable number of arguments for other\n> > reasons, making the function in a way have to variable length lists of\n> > arguments)\n>\n> In my WIP fixups, I refactored this into a version that takes varargs and\n> another version that takes a string_list.\n>\n> However, after getting all this to compile and t4301 to pass, I think we\n> actually only need a version that takes up to two \"other\" paths, and a\n> version that takes a string_list with those \"other\" paths, where the\n> former constructs a temporary string_list and then calls the latter.\n\nI ended up refactoring the refactor. The `path_msg()` function now takes\nthe \"other paths\" in two different forms: it accepts two (optional) `const\nchar*` parameters and an (also optional) `struct string_list *`. Either of\nthem, if non-NULL, will be added to the `struct strvec` field.\n\nThis looks _slightly_ ugly to me, but from an implementation point is the\ncleanest solution.\n\n> >   * The new enums and structs I added to merge-ort.c really have to be\n> > added to merge-ort.h and become part of the API.  Feels a little\n> > unfortunate since it'll make the API _much_ more involved, but I don't\n> > see any other way to solve your usecase.\n>\n> I agree, but I did not do that yet ;-)\n>\n> Another thing I noticed is that we can probably ensure consistency between\n> the `conflict_and_info_types` enum and the `type_short_descriptions` array\n> by using the same C99 construct we're already using in the\n> `advice_setting` array in advice.c:\n>\n> \tstatic const char *type_short_descriptions[NB_CONFLICT_TYPES] = {\n> \t\t/*** \"Simple\" conflicts and informational messages ***/\n> \t\t[INFO_AUTO_MERGING] = \"Auto-merging\",\n> \t\t[CONFLICT_CONTENTS] = \"CONFLICT (contents)\",\n> \t[...]\n\nStill haven't done that, either, as it is merely syntactic sugar, really,\nand not really an interesting change. I think I'll leave that to a time\nafter I managed to modify the remerge-diff machinery to accept the\nnew-style `conflicts` map (instead of recreating the old-style `output`\nmap, as I am doing for now).\n\n> It would be great if you could have a quick look over the commits I\n> added on top of your branch, to see whether things make more or less\n> sense to you. But if you're too busy elsewhere, I am one of the best\n> persons to understand that, too.\n\nHopefully I will get this into a reviewable shape before you get to\nlooking at it, so that your time is spent more wisely than what I asked\nfor ;-)\n\nCiao,\nDscho\n"},{"id":"456702","messageId":"nycvar.QRO.7.76.6.2206060019510.349@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2206051733040.349@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-06-05T22:42:01Z","receivedAt":"2022-06-05T22:42:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\n[sorry for sending this flurry of mails, I just wasn't sure how\nconsecutively I could work on the `merge-tree` patches, and therefore sent\nmails whenever I had to take a break in case I wouldn't be able to get\nback to this project for a couple of days.]\n\nOn Sun, 5 Jun 2022, Johannes Schindelin wrote:\n\n> On Sat, 4 Jun 2022, Johannes Schindelin wrote:\n>\n> > On Tue, 17 May 2022, Elijah Newren wrote:\n> >\n> > > * The previous \"output\" member of merge_result, containing a strmap\n> > > of conflict and informational messages (basically a mapping of\n> > > filename -> strbuf) now needs to be replaced by a strmap\n> > > \"conflicts\", which is now a mapping of primary_filename ->\n> > > logical_conflicts, and logical_conflicts is an array of\n> > > logical_conflict, and logical_conflict has a type, array of paths,\n> > > and message.\n\nI massaged this a bit further: since we no longer actually need a `strbuf`\nthere (we no longer append to the `strbuf` but instead to the list of\nlogical conflicts), I replaced `struct logical_conflicts` with a\n`string_list` where each item contains the conflict message and its `util`\npoints to a `struct logical_merge_info` that contains the `type` and the\ninvolved paths.\n\nThis lets me...\n\n> > >   * Since \"output\" is no longer part of merge_result, the new\n> > > remerge-diff functionality is going to need to be modified since it\n> > > used that field, and instead iterate on \"conflicts\" to get the same\n> > > information\n> >\n> > I punted on that for now, recreating an `output`-style strmap and storing\n> > it as `path_messages` attribute.\n>\n> I still punted on that because I wanted to see whether I could address the\n> test suite failures first (narrator's voice: he could).\n\n... address this issue without resorting to declaring the `logical_merge`\nstruct in `merge-ort.h` (which would get a bit messy, not only because we\nwould have to `#include <strvec.h>` in that header because that struct\ncontains a `struct strvec paths` and therefore must know the storage size\nof `strvec`, but also because `merge-ort.h` is not included in `diff.c`,\nand it would be a bit iffy to do that).\n\nSince the per-path conflicts are now stored as a `string_list`, and since\nwe only need the messages in remerge, we can continue to simply pass the\npointer to `strmap conflicts` to the remerge machinery (in the common\ncase, if pathspecs are involved, we continue to lightly copy-filter that\n`strmap`).\n\n> > >   * The new enums and structs I added to merge-ort.c really have to be\n> > > added to merge-ort.h and become part of the API.  Feels a little\n> > > unfortunate since it'll make the API _much_ more involved, but I don't\n> > > see any other way to solve your usecase.\n> >\n> > I agree, but I did not do that yet ;-)\n\nAs mentioned above, I think the better way is to _not_ declare that enum\nand structs in `merge-ort.h` and instead store the per-path conflicts as a\n`string_list` whose strings contain the conflict message and whose `util`\ncontains the type and the list of involved paths.\n\nThis simplifies the API rather dramatically, since the current user\n(remerge) is only interested in the conflict message, but not in the type\nnor in the full list of involved paths.\n\nShould we ever need to access the type or the paths outside of\n`merge-ort.c`, it is easy enough to add a simple function to access that\ninformation via the `string_list`'s `util`.\n\n> > Another thing I noticed is that we can probably ensure consistency between\n> > the `conflict_and_info_types` enum and the `type_short_descriptions` array\n> > by using the same C99 construct we're already using in the\n> > `advice_setting` array in advice.c:\n> >\n> > \tstatic const char *type_short_descriptions[NB_CONFLICT_TYPES] = {\n> > \t\t/*** \"Simple\" conflicts and informational messages ***/\n> > \t\t[INFO_AUTO_MERGING] = \"Auto-merging\",\n> > \t\t[CONFLICT_CONTENTS] = \"CONFLICT (contents)\",\n> > \t[...]\n>\n> Still haven't done that, either, as it is merely syntactic sugar, really,\n> and not really an interesting change. I think I'll leave that to a time\n> after I managed to modify the remerge-diff machinery to accept the\n> new-style `conflicts` map (instead of recreating the old-style `output`\n> map, as I am doing for now).\n\nSince I addressed that `output` issue, I now also C99-ified the\n`type_short_descriptions` array.\n\n> > It would be great if you could have a quick look over the commits I\n> > added on top of your branch, to see whether things make more or less\n> > sense to you. But if you're too busy elsewhere, I am one of the best\n> > persons to understand that, too.\n>\n> Hopefully I will get this into a reviewable shape before you get to\n> looking at it, so that your time is spent more wisely than what I asked\n> for ;-)\n\nI hope to find some time to work on this more tomorrow; If not, I will get\nback to the project on Wednesday and push it further.\n\nCiao,\nDscho\n"},{"id":"456755","messageId":"nycvar.QRO.7.76.6.2206062325020.349@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2206060019510.349@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-06-06T21:37:25Z","receivedAt":"2022-06-06T21:37:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\n[after this update, I will shut up until you chime in, promise!]\n\nOn Mon, 6 Jun 2022, Johannes Schindelin wrote:\n\n> On Sun, 5 Jun 2022, Johannes Schindelin wrote:\n>\n> > On Sat, 4 Jun 2022, Johannes Schindelin wrote:\n> >\n> > > It would be great if you could have a quick look over the commits I\n> > > added on top of your branch, to see whether things make more or less\n> > > sense to you. But if you're too busy elsewhere, I am one of the best\n> > > persons to understand that, too.\n> >\n> > Hopefully I will get this into a reviewable shape before you get to\n> > looking at it, so that your time is spent more wisely than what I\n> > asked for ;-)\n>\n> I hope to find some time to work on this more tomorrow; If not, I will\n> get back to the project on Wednesday and push it further.\n\nI did get a chance, and polished the patch series a bit. I also rebased it\nonto the current tip of Git's main branch, mainly to address some merge\nconflicts preemptively. The result is pushed up to\nhttps://github.com/dscho/git/tree/js/in-core-merge-tree. This is the\nrange-diff relative to `newren/wip-for-in-core-merge-tree`:\n\n-- snip --\n 1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases\n 2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb\n 3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability\n 4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`\n 5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings\n 6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion\n 7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable\n 8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers\n 9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths\n10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers\n11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable\n12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges\n13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()\n14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function\n15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function\n16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges\n17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function\n18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output\n19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function\n25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort\n    @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n      \t\tstrbuf_release(&sb);\n      \t\tbreak;\n      \tdefault:\n    +\n    + ## t/t6437-submodule-merge.sh ##\n    +@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '\n    + \t(cd merge-search &&\n    + \t git checkout -b test-nonforward b &&\n    + \t (cd sub &&\n    +-\t  git rev-parse sub-d > ../expect) &&\n    ++\t  git rev-parse --short sub-d > ../expect) &&\n    + \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n    + \t  then\n    + \t\ttest_must_fail git merge c >actual\n20:  7b1ee417f3d !  9:  c92b81e7366 merge-tree: provide a list of which files have conflicts\n    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n     +\t\tstring_list_clear(&conflicted_files, 1);\n     +\t}\n      \tif (o->show_messages) {\n    - \t\tprintf(\"\\n\");\n    +-\t\tprintf(\"\\n\");\n    ++\t\tputchar(line_termination);\n      \t\tmerge_display_update_messages(&opt, &result);\n    + \t}\n    + \tmerge_finalize(&opt, &result);\n     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n\n      \t/* Do the relevant type of merge */\n21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info\n -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf\n -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list\n -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information\n -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output\n22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated\n    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n      \tif (!result.clean) {\n      \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n      \t\tconst char *last = NULL;\n    -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n    - \t\tstring_list_clear(&conflicted_files, 1);\n    - \t}\n    - \tif (o->show_messages) {\n    --\t\tprintf(\"\\n\");\n    -+\t\tputchar(line_termination);\n    - \t\tmerge_display_update_messages(&opt, &result);\n    - \t}\n    - \tmerge_finalize(&opt, &result);\n     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n      \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n      \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n     +\n     +\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n     +\tanonymize_hash out >actual &&\n    ++\tprintf \"\\\\n\" >>actual &&\n     +\n     +\t# Expected results:\n     +\t#   \"greeting\" should merge with conflicts\n    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n     +\n     +\tEOF\n     +\n    -+\tcat <<-EOF >>expect &&\n    -+\tAuto-merging greeting\n    -+\tCONFLICT (content): Merge conflict in greeting\n    -+\tCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n    -+\tCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n    -+\tAuto-merging Αυτά μου φαίνονται κινέζικα\n    -+\tCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n    ++\tq_to_nul <<-EOF >>expect &&\n    ++\t1QgreetingQAuto-mergingQAuto-merging greeting\n    ++\tQ1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting\n    ++\tQ2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n    ++\tQ1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n    ++\tQ1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα\n    ++\tQ1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n    ++\tQ\n     +\tEOF\n     +\n     +\ttest_cmp expect actual\n23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag\n24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes\n    @@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:\n     +<<IM,Informational messages>> section has the necessary info, though it\n     +is not designed to be machine parseable.\n     +\n    ++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and\n    ++the logical conflicts in the <<IM,Informational messages>> have a\n    ++one-to-one mapping, nor that there is a one-to-many mapping, nor a\n    ++many-to-one mapping.  Many-to-many mappings exist, meaning that each\n    ++path can have many logical conflict types in a single merge, and each\n    ++logical conflict type can affect many paths.\n    ++\n     +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n     +section had conflicts.  Messages can be included for files that have no\n     +conflicts, such as \"Auto-merging <file>\".\n26:  78e1243eca1 <  -:  ----------- WIP\n-- snap --\n\nI am pretty happy with the current state of the patches, and hope that we\ncan push this patch series over the finish line.\n\nIf you can think of anything I can do to help with this, please do let me\nknow, I am _very_ interested in getting this done, and finally am in a\nposition to help.\n\nCiao,\nDscho\n"},{"id":"456770","messageId":"CABPp-BF3JwBGugN25RKxwTUQ6BDZL6OUwgLPVZQdj3d1mFdBgQ@mail.gmail.com","threadId":"57288","inReplyTo":"nycvar.QRO.7.76.6.2206062325020.349@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-06-07T07:38:35Z","receivedAt":"2022-06-07T07:38:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nOn Mon, Jun 6, 2022 at 2:37 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Elijah,\n>\n> [after this update, I will shut up until you chime in, promise!]\n>\n> On Mon, 6 Jun 2022, Johannes Schindelin wrote:\n>\n> > On Sun, 5 Jun 2022, Johannes Schindelin wrote:\n> >\n> > > On Sat, 4 Jun 2022, Johannes Schindelin wrote:\n> > >\n> > > > It would be great if you could have a quick look over the commits I\n> > > > added on top of your branch, to see whether things make more or less\n> > > > sense to you. But if you're too busy elsewhere, I am one of the best\n> > > > persons to understand that, too.\n> > >\n> > > Hopefully I will get this into a reviewable shape before you get to\n> > > looking at it, so that your time is spent more wisely than what I\n> > > asked for ;-)\n> >\n> > I hope to find some time to work on this more tomorrow; If not, I will\n> > get back to the project on Wednesday and push it further.\n>\n> I did get a chance, and polished the patch series a bit. I also rebased it\n> onto the current tip of Git's main branch, mainly to address some merge\n> conflicts preemptively. The result is pushed up to\n> https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the\n> range-diff relative to `newren/wip-for-in-core-merge-tree`:\n\nThis is so awesome; thanks for working on this.  Sorry I haven't had\ntime to review yet, but I'm hoping to be able to near the end of this\nweek.  I'm excited to see how it looks.  :-)\n\n> -- snip --\n>  1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases\n>  2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb\n>  3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability\n>  4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`\n>  5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings\n>  6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion\n>  7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable\n>  8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers\n>  9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths\n> 10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers\n> 11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable\n> 12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges\n> 13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()\n> 14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function\n> 15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function\n> 16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges\n> 17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function\n> 18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output\n> 19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function\n> 25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort\n>     @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n>                 strbuf_release(&sb);\n>                 break;\n>         default:\n>     +\n>     + ## t/t6437-submodule-merge.sh ##\n>     +@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '\n>     +   (cd merge-search &&\n>     +    git checkout -b test-nonforward b &&\n>     +    (cd sub &&\n>     +-    git rev-parse sub-d > ../expect) &&\n>     ++    git rev-parse --short sub-d > ../expect) &&\n>     +     if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n>     +     then\n>     +           test_must_fail git merge c >actual\n> 20:  7b1ee417f3d !  9:  c92b81e7366 merge-tree: provide a list of which files have conflicts\n>     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n>      +          string_list_clear(&conflicted_files, 1);\n>      +  }\n>         if (o->show_messages) {\n>     -           printf(\"\\n\");\n>     +-          printf(\"\\n\");\n>     ++          putchar(line_termination);\n>                 merge_display_update_messages(&opt, &result);\n>     +   }\n>     +   merge_finalize(&opt, &result);\n>      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>\n>         /* Do the relevant type of merge */\n> 21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info\n>  -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf\n>  -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list\n>  -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information\n>  -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output\n> 22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated\n>     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n>         if (!result.clean) {\n>                 struct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n>                 const char *last = NULL;\n>     -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n>     -           string_list_clear(&conflicted_files, 1);\n>     -   }\n>     -   if (o->show_messages) {\n>     --          printf(\"\\n\");\n>     -+          putchar(line_termination);\n>     -           merge_display_update_messages(&opt, &result);\n>     -   }\n>     -   merge_finalize(&opt, &result);\n>      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n>                             N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n>                 OPT_BOOL(0, \"messages\", &o.show_messages,\n>     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n>      +\n>      +  test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n>      +  anonymize_hash out >actual &&\n>     ++  printf \"\\\\n\" >>actual &&\n>      +\n>      +  # Expected results:\n>      +  #   \"greeting\" should merge with conflicts\n>     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n>      +\n>      +  EOF\n>      +\n>     -+  cat <<-EOF >>expect &&\n>     -+  Auto-merging greeting\n>     -+  CONFLICT (content): Merge conflict in greeting\n>     -+  CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n>     -+  CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n>     -+  Auto-merging Αυτά μου φαίνονται κινέζικα\n>     -+  CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n>     ++  q_to_nul <<-EOF >>expect &&\n>     ++  1QgreetingQAuto-mergingQAuto-merging greeting\n>     ++  Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting\n>     ++  Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n>     ++  Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n>     ++  Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα\n>     ++  Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n>     ++  Q\n>      +  EOF\n>      +\n>      +  test_cmp expect actual\n> 23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag\n> 24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes\n>     @@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:\n>      +<<IM,Informational messages>> section has the necessary info, though it\n>      +is not designed to be machine parseable.\n>      +\n>     ++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and\n>     ++the logical conflicts in the <<IM,Informational messages>> have a\n>     ++one-to-one mapping, nor that there is a one-to-many mapping, nor a\n>     ++many-to-one mapping.  Many-to-many mappings exist, meaning that each\n>     ++path can have many logical conflict types in a single merge, and each\n>     ++logical conflict type can affect many paths.\n>     ++\n>      +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n>      +section had conflicts.  Messages can be included for files that have no\n>      +conflicts, such as \"Auto-merging <file>\".\n> 26:  78e1243eca1 <  -:  ----------- WIP\n> -- snap --\n>\n> I am pretty happy with the current state of the patches, and hope that we\n> can push this patch series over the finish line.\n>\n> If you can think of anything I can do to help with this, please do let me\n> know, I am _very_ interested in getting this done, and finally am in a\n> position to help.\n\nVery much appreciated.  Looks like you're now blocking on my review,\nso I'll try to make some time by end of week to look over things.\n"},{"id":"457467","messageId":"CABPp-BFfRGQybYManC6LfFTaAd3mweBxL=APCGP_vRF-WQxGSw@mail.gmail.com","threadId":"57288","inReplyTo":"CABPp-BF3JwBGugN25RKxwTUQ6BDZL6OUwgLPVZQdj3d1mFdBgQ@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-06-17T23:44:12Z","receivedAt":"2022-06-17T23:44:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jun 7, 2022 at 12:38 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> Hi Dscho,\n>\n> On Mon, Jun 6, 2022 at 2:37 PM Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> >\n> > Hi Elijah,\n> >\n> > [after this update, I will shut up until you chime in, promise!]\n> >\n> > On Mon, 6 Jun 2022, Johannes Schindelin wrote:\n> >\n> > > On Sun, 5 Jun 2022, Johannes Schindelin wrote:\n> > >\n> > > > On Sat, 4 Jun 2022, Johannes Schindelin wrote:\n> > > >\n> > > > > It would be great if you could have a quick look over the commits I\n> > > > > added on top of your branch, to see whether things make more or less\n> > > > > sense to you. But if you're too busy elsewhere, I am one of the best\n> > > > > persons to understand that, too.\n> > > >\n> > > > Hopefully I will get this into a reviewable shape before you get to\n> > > > looking at it, so that your time is spent more wisely than what I\n> > > > asked for ;-)\n> > >\n> > > I hope to find some time to work on this more tomorrow; If not, I will\n> > > get back to the project on Wednesday and push it further.\n> >\n> > I did get a chance, and polished the patch series a bit. I also rebased it\n> > onto the current tip of Git's main branch, mainly to address some merge\n> > conflicts preemptively. The result is pushed up to\n> > https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the\n> > range-diff relative to `newren/wip-for-in-core-merge-tree`:\n>\n> This is so awesome; thanks for working on this.  Sorry I haven't had\n> time to review yet, but I'm hoping to be able to near the end of this\n> week.  I'm excited to see how it looks.  :-)\n>\n> > -- snip --\n> >  1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases\n> >  2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb\n> >  3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability\n> >  4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`\n> >  5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings\n> >  6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion\n> >  7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable\n> >  8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers\n> >  9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths\n> > 10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers\n> > 11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable\n> > 12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges\n> > 13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()\n> > 14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function\n> > 15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function\n> > 16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges\n> > 17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function\n> > 18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output\n> > 19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function\n> > 25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort\n> >     @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n> >                 strbuf_release(&sb);\n> >                 break;\n> >         default:\n> >     +\n> >     + ## t/t6437-submodule-merge.sh ##\n> >     +@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '\n> >     +   (cd merge-search &&\n> >     +    git checkout -b test-nonforward b &&\n> >     +    (cd sub &&\n> >     +-    git rev-parse sub-d > ../expect) &&\n> >     ++    git rev-parse --short sub-d > ../expect) &&\n> >     +     if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> >     +     then\n> >     +           test_must_fail git merge c >actual\n> > 20:  7b1ee417f3d !  9:  c92b81e7366 merge-tree: provide a list of which files have conflicts\n> >     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n> >      +          string_list_clear(&conflicted_files, 1);\n> >      +  }\n> >         if (o->show_messages) {\n> >     -           printf(\"\\n\");\n> >     +-          printf(\"\\n\");\n> >     ++          putchar(line_termination);\n> >                 merge_display_update_messages(&opt, &result);\n> >     +   }\n> >     +   merge_finalize(&opt, &result);\n> >      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >\n> >         /* Do the relevant type of merge */\n> > 21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info\n> >  -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf\n> >  -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list\n> >  -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information\n> >  -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output\n> > 22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated\n> >     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n> >         if (!result.clean) {\n> >                 struct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n> >                 const char *last = NULL;\n> >     -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n> >     -           string_list_clear(&conflicted_files, 1);\n> >     -   }\n> >     -   if (o->show_messages) {\n> >     --          printf(\"\\n\");\n> >     -+          putchar(line_termination);\n> >     -           merge_display_update_messages(&opt, &result);\n> >     -   }\n> >     -   merge_finalize(&opt, &result);\n> >      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> >                             N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n> >                 OPT_BOOL(0, \"messages\", &o.show_messages,\n> >     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n> >      +\n> >      +  test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n> >      +  anonymize_hash out >actual &&\n> >     ++  printf \"\\\\n\" >>actual &&\n> >      +\n> >      +  # Expected results:\n> >      +  #   \"greeting\" should merge with conflicts\n> >     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n> >      +\n> >      +  EOF\n> >      +\n> >     -+  cat <<-EOF >>expect &&\n> >     -+  Auto-merging greeting\n> >     -+  CONFLICT (content): Merge conflict in greeting\n> >     -+  CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n> >     -+  CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n> >     -+  Auto-merging Αυτά μου φαίνονται κινέζικα\n> >     -+  CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n> >     ++  q_to_nul <<-EOF >>expect &&\n> >     ++  1QgreetingQAuto-mergingQAuto-merging greeting\n> >     ++  Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting\n> >     ++  Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n> >     ++  Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n> >     ++  Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα\n> >     ++  Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n> >     ++  Q\n> >      +  EOF\n> >      +\n> >      +  test_cmp expect actual\n> > 23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag\n> > 24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes\n> >     @@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:\n> >      +<<IM,Informational messages>> section has the necessary info, though it\n> >      +is not designed to be machine parseable.\n> >      +\n> >     ++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and\n> >     ++the logical conflicts in the <<IM,Informational messages>> have a\n> >     ++one-to-one mapping, nor that there is a one-to-many mapping, nor a\n> >     ++many-to-one mapping.  Many-to-many mappings exist, meaning that each\n> >     ++path can have many logical conflict types in a single merge, and each\n> >     ++logical conflict type can affect many paths.\n> >     ++\n> >      +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n> >      +section had conflicts.  Messages can be included for files that have no\n> >      +conflicts, such as \"Auto-merging <file>\".\n> > 26:  78e1243eca1 <  -:  ----------- WIP\n> > -- snap --\n> >\n> > I am pretty happy with the current state of the patches, and hope that we\n> > can push this patch series over the finish line.\n> >\n> > If you can think of anything I can do to help with this, please do let me\n> > know, I am _very_ interested in getting this done, and finally am in a\n> > position to help.\n>\n> Very much appreciated.  Looks like you're now blocking on my review,\n> so I'll try to make some time by end of week to look over things.\n\nSorry for the delay.  However, I have looked over the changes in\ndetail.  Well done!  I very much like this version.  Thanks for taking\nthe time to break up my WIP patches, fix my bugs, address all the\nFIXMEs, and then going and adding some nice cleanups and improvements\non top!  I'll add my Signed-off-by on a couple patches and have\ngitgitgadget submit this round.\n"},{"id":"457468","messageId":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v6.git.1645602413.gitgitgadget@gmail.com","subject":"[PATCH v7 00/17] In-core git merge-tree (\"Server side merges\")","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:43Z","receivedAt":"2022-06-18T00:21:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Sorry for the long delay. Much thanks to Johannes for tying up the loose\nends and making several improvements seen in this round. Note: rebased on\nmain (it's been 4 months since v6, and there were a few conflicts...).\n\n== Basic Summary ==\n\nThis series introduces a new mode to git merge-tree allowing it to perform\nreal merges (three-way text content merges, recursive ancestor\nconsolidation, rename detection, proper directory/file conflict handling,\netc.) and write the result as a toplevel tree. It doesn't touch the working\ntree or index, and doesn't create any commits or update any refs. It could\nbe used to do merges when in a bare repository (thus potentially making it\nof interest to Git hosting sites, i.e. \"Server side merges\"), or for doing a\nmerge of branches that aren't checked out.\n\nIt does not handle similar functionality for cherry-picks, rebases, or\nreverts; that is also of interest, but is being deferred for a future\nseries.\n\n== Quick Overview ==\n\n * Patches 1-2: preparatory cleanups\n * Patches 3-4: implement basic real merges\n * Patches 5-6: include informational messages (\"CONFLICT\" messages and\n   such) in output\n * Patches 7-15: add ability to include ls-files -u style of info in the\n   output\n * Patch 16: support --allow-unrelated-histories\n * Patch 17: augment the manual with potential usage mistakes\n\n== Updates Log ==\n\nStuff NOT included that reviewers brought up in various rounds (and which\nmight still be an open question):\n\n * Very generic (mode, oid, stage, filename) printing formatting[3]\n * Providing similar functionality for doing cherry-picks/rebases/reverts,\n   i.e. a scheme for three-way merges with a specified merge-base[4]. That's\n   being deferred to a future series. [3]\n   https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/\n   [4]\n   https://lore.kernel.org/git/CABPp-BEaemkGGm0cSofP0gau7YN-y6HFoi0yJbHA8+iGjxsYSA@mail.gmail.com/\n\nUpdates since v6:\n\n * Significant merge-ort changes to support machine-parseable information\n   about individual conflicts and the paths involved in each, rather than\n   just a group of free-form output messages that may change over time. This\n   includes 5 new patches, 2 written by Johannes (and with breaking out the\n   other 3 from some work-in-progress changes I had including some fixes and\n   improvements he added).\n\nUpdates since v5:\n\n * Used reverse_commit_list() to reverse a commit_list without extra\n   allocations\n * Several documentation updates\n\nUpdates since v4:\n\n * Fixed double \"is\" in documentation.\n * Fixed a few small items with testcases\n\nUpdates since v3:\n\n * Dropped previous patches 5, 6, and 8 of the old series; they weren't\n   being used and opened a can of worms[1]\n * [Patch 3] Restructured argument checking, including using an enum\n * [Patch 4] Restored the extended paragraph about the deprecated form of\n   git-merge-tree, mentioned write-tree in plumbing commands, and a few\n   other small fixups to the documentation\n * [Patch 4] Also provide an example of a clean merge rather than just a\n   conflicted one\n * [Patch 6] Fix the incompatible arguments check and add some tests for it\n * [Patch 6] Introduce an anonymize_hash() shell function to make tests\n   easier to read (less repeated sed)\n * [Patch 9] Rename --exclude-modes-oids-stages to --name-only; no short\n   option for now\n * [Patch 10] When -z passed, the tree in the first section should have a\n   trailing NUL rather than trailing newline [1]\n   https://lore.kernel.org/git/CABPp-BEKuXHELVx4=5JJTj5HVOKZ=Y-4G4BK47BCZYYRSrkFsQ@mail.gmail.com/\n\nUpdates since v2:\n\n * Improved patches from Dscho for the diff_warn_rename_limit() handling\n * Add a -z option for NUL-terminated conflict info lines (so that filenames\n   do not have to be quoted)\n\nUpdates since v1 (or v3 depending on how you count; thanks to René, Ævar,\nChristian, Dscho for very helpful feedback):\n\n * New patch from Dscho allowing diff_warn_rename_limit() to print somewhere\n   other than stdout (I hope he's okay with me including his Signed-off-by)\n * Now prints filenames relative to prefix, much like ls-files\n * Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave\n   it a -l shorthand; I'm wondering if I should just drop this option,\n   though.\n * And numerous cleanups, in lots of areas:\n   * Multiple parse-options cleanups\n   * Lots of commit message cleanups\n   * Wording tweaks to the \"Description\" section of the manual\n   * Several small code cleanups\n * I dropped the RFC label\n\n[There were also two submissions of a previous series; see\nhttps://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/]\n[https://lore.kernel.org/git/pull.1114.v2.git.git.1641403655.gitgitgadget@gmail.com/%5D]\n\nUpdates since original submission v2 (thanks to Christian, Dscho, Ramsay,\nand René for suggestions and comments):\n\n * Significant changes to output format:\n   * Flags no longer take a filename for additional output; they write to\n     stdout instead.\n   * More information included by default when there are conflicts (no need\n     to request it with additional flags, instead flags can be used to\n     suppress it).\n   * Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of\n     information -- when there are conflicts. Add a flag to only list\n     conflicted files if that's preferred.\n * Much more thorough manual for git-merge-tree.txt\n * Renamed option from --real to --write-tree\n * Accept an optional --trivial-merge option to get old style merge-tree\n   behavior\n * Allow both --write-tree and --trivial-merge to be omitted since we can\n   deduce which from number of arguments\n * Document exit code when the merge cannot be run (so we can distinguish\n   other error cases from conflicts)\n * testcase cleanups: test_tick, early skip of test when using recursive\n   backend, variable renames, etc.\n * various minor code cleanups\n * Add a new --allow-unrelated-histories option (with same meaning as the\n   one used in git merge)\n * Rebased on top of en/remerge-diff to avoid a small conflict\n\nUpdates since original submission v1 (thanks to Johannes Altmanninger and\nFabian for suggestions):\n\n * Fixed a bad patch splitting, and a style issue pointed out by Johannes\n   Altimanninger\n * Fixed misleading commit messages in new test cases\n * Fixed my comments about how commit-tree could be used to correctly use\n   two -p flags\n\nElijah Newren (15):\n  merge-tree: rename merge_trees() to trivial_merge_trees()\n  merge-tree: move logic for existing merge into new function\n  merge-tree: add option parsing and initial shell for real merge\n    function\n  merge-tree: implement real merges\n  merge-ort: split out a separate display_update_messages() function\n  merge-tree: support including merge messages in output\n  merge-ort: provide a merge_get_conflicted_files() helper function\n  merge-ort: remove command-line-centric submodule message from\n    merge-ort\n  merge-tree: provide a list of which files have conflicts\n  merge-tree: provide easy access to `ls-files -u` style info\n  merge-ort: store more specific conflict information\n  merge-ort: optionally produce machine-readable output\n  merge-tree: allow `ls-files -u` style info to be NUL terminated\n  merge-tree: add a --allow-unrelated-histories flag\n  git-merge-tree.txt: add a section on potentional usage mistakes\n\nJohannes Schindelin (2):\n  merge-ort: store messages in a list, not in a single strbuf\n  merge-ort: make `path_messages` a strmap to a string_list\n\n Documentation/git-merge-tree.txt | 235 ++++++++++++++-\n builtin/merge-tree.c             | 187 +++++++++++-\n diff.c                           |  27 +-\n git.c                            |   2 +-\n merge-ort.c                      | 474 ++++++++++++++++++++++---------\n merge-ort.h                      |  32 ++-\n t/t4301-merge-tree-write-tree.sh | 240 ++++++++++++++++\n t/t6437-submodule-merge.sh       |   2 +-\n 8 files changed, 1032 insertions(+), 167 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\n\nbase-commit: ab336e8f1c8009c8b1aab8deb592148e69217085\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/1122\n\nRange-diff vs v6:\n\n  1:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()\n  2:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function\n  3:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function\n  4:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges\n  5:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function\n  6:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output\n  7:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function\n  -:  ----------- >  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort\n  8:  7b1ee417f3d !  9:  c92b81e7366 merge-tree: provide a list of which files have conflicts\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n      +\t\tstring_list_clear(&conflicted_files, 1);\n      +\t}\n       \tif (o->show_messages) {\n     - \t\tprintf(\"\\n\");\n     +-\t\tprintf(\"\\n\");\n     ++\t\tputchar(line_termination);\n       \t\tmerge_display_update_messages(&opt, &result);\n     + \t}\n     + \tmerge_finalize(&opt, &result);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n       \n       \t/* Do the relevant type of merge */\n  9:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info\n  -:  ----------- > 11:  f523b08ab5a merge-ort: store messages in a list, not in a single strbuf\n  -:  ----------- > 12:  6b47c0fdbd7 merge-ort: make `path_messages` a strmap to a string_list\n  -:  ----------- > 13:  7eb70f77c81 merge-ort: store more specific conflict information\n  -:  ----------- > 14:  662e97f2ed4 merge-ort: optionally produce machine-readable output\n 10:  22297e6ce75 ! 15:  b314aa9c436 merge-tree: allow `ls-files -u` style info to be NUL terminated\n     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n       \tif (!result.clean) {\n       \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n       \t\tconst char *last = NULL;\n     -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n     - \t\tstring_list_clear(&conflicted_files, 1);\n     - \t}\n     - \tif (o->show_messages) {\n     --\t\tprintf(\"\\n\");\n     -+\t\tputchar(line_termination);\n     - \t\tmerge_display_update_messages(&opt, &result);\n     - \t}\n     - \tmerge_finalize(&opt, &result);\n      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n       \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n       \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n      +\n      +\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n      +\tanonymize_hash out >actual &&\n     ++\tprintf \"\\\\n\" >>actual &&\n      +\n      +\t# Expected results:\n      +\t#   \"greeting\" should merge with conflicts\n     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n      +\n      +\tEOF\n      +\n     -+\tcat <<-EOF >>expect &&\n     -+\tAuto-merging greeting\n     -+\tCONFLICT (content): Merge conflict in greeting\n     -+\tCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n     -+\tCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n     -+\tAuto-merging Αυτά μου φαίνονται κινέζικα\n     -+\tCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n     ++\tq_to_nul <<-EOF >>expect &&\n     ++\t1QgreetingQAuto-mergingQAuto-merging greeting\n     ++\tQ1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting\n     ++\tQ2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n     ++\tQ1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n     ++\tQ1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα\n     ++\tQ1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n     ++\tQ\n      +\tEOF\n      +\n      +\ttest_cmp expect actual\n 11:  db73c6dd823 = 16:  66df0c2e837 merge-tree: add a --allow-unrelated-histories flag\n 12:  d58a7c7a9f6 ! 17:  70ea8281952 git-merge-tree.txt: add a section on potentional usage mistakes\n     @@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:\n      +<<IM,Informational messages>> section has the necessary info, though it\n      +is not designed to be machine parseable.\n      +\n     ++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and\n     ++the logical conflicts in the <<IM,Informational messages>> have a\n     ++one-to-one mapping, nor that there is a one-to-many mapping, nor a\n     ++many-to-one mapping.  Many-to-many mappings exist, meaning that each\n     ++path can have many logical conflict types in a single merge, and each\n     ++logical conflict type can affect many paths.\n     ++\n      +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n      +section had conflicts.  Messages can be included for files that have no\n      +conflicts, such as \"Auto-merging <file>\".\n\n-- \ngitgitgadget\n"},{"id":"457469","messageId":"8fb51817ed4688cd8eadb30108f96f741405bb8e.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 01/17] merge-tree: rename merge_trees() to trivial_merge_trees()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:44Z","receivedAt":"2022-06-18T00:21:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-recursive.h defined its own merge_trees() function, different than\nthe one found in builtin/merge-tree.c.  That was okay in the past, but\nwe want merge-tree to be able to use the merge-ort functions, which will\nend up including merge-recursive.h.  Rename the function found in\nbuiltin/merge-tree.c to avoid the conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 5dc94d6f880..06f9eee9f78 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)\n \tmerge_result_end = &entry->next;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base);\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base);\n \n static const char *explanation(struct merge_list *entry)\n {\n@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,\n \tbuf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));\n #undef ENTRY_OID\n \n-\tmerge_trees(t, newbase);\n+\ttrivial_merge_trees(t, newbase);\n \n \tfree(buf0);\n \tfree(buf1);\n@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s\n \treturn mask;\n }\n \n-static void merge_trees(struct tree_desc t[3], const char *base)\n+static void trivial_merge_trees(struct tree_desc t[3], const char *base)\n {\n \tstruct traverse_info info;\n \n@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n-\tmerge_trees(t, \"\");\n+\ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n \tfree(buf3);\n-- \ngitgitgadget\n\n"},{"id":"457470","messageId":"8e0a79fa1ad0e1a86617e7f73f5534f5db9818e3.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 02/17] merge-tree: move logic for existing merge into new function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:45Z","receivedAt":"2022-06-18T00:21:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIn preparation for adding a non-trivial merge capability to merge-tree,\nmove the existing merge logic for trivial merges into a new function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 06f9eee9f78..914ec960b7e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+static int trivial_merge(int argc, const char **argv)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\n \tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n \tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n \tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \tshow_result();\n \treturn 0;\n }\n+\n+int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tif (argc != 4)\n+\t\tusage(merge_tree_usage);\n+\treturn trivial_merge(argc, argv);\n+}\n-- \ngitgitgadget\n\n"},{"id":"457471","messageId":"baf0950bcb61d22491fa015f5270eb49bf6d66a7.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 03/17] merge-tree: add option parsing and initial shell for real merge function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:46Z","receivedAt":"2022-06-18T00:21:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nLet merge-tree accept a `--write-tree` parameter for choosing real\nmerges instead of trivial merges, and accept an optional\n`--trivial-merge` option to get the traditional behavior.  Note that\nthese accept different numbers of arguments, though, so these names\nneed not actually be used.\n\nNote that real merges differ from trivial merges in that they handle:\n  - three way content merges\n  - recursive ancestor consolidation\n  - renames\n  - proper directory/file conflict handling\n  - etc.\nBasically all the stuff you'd expect from `git merge`, just without\nupdating the index and working tree.  The initial shell added here does\nnothing more than die with \"real merges are not yet implemented\", but\nthat will be fixed in subsequent commits.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge-tree.c | 84 +++++++++++++++++++++++++++++++++++++++-----\n git.c                |  2 +-\n 2 files changed, 76 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 914ec960b7e..0f9d928e862 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -3,13 +3,12 @@\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n #include \"object-store.h\"\n+#include \"parse-options.h\"\n #include \"repository.h\"\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n \n-static const char merge_tree_usage[] = \"git merge-tree <base-tree> <branch1> <branch2>\";\n-\n struct merge_list {\n \tstruct merge_list *next;\n \tstruct merge_list *link;\t/* other stages for this object */\n@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,\n \treturn buf;\n }\n \n-static int trivial_merge(int argc, const char **argv)\n+static int trivial_merge(const char *base,\n+\t\t\t const char *branch1,\n+\t\t\t const char *branch2)\n {\n \tstruct repository *r = the_repository;\n \tstruct tree_desc t[3];\n \tvoid *buf1, *buf2, *buf3;\n \n-\tbuf1 = get_tree_descriptor(r, t+0, argv[1]);\n-\tbuf2 = get_tree_descriptor(r, t+1, argv[2]);\n-\tbuf3 = get_tree_descriptor(r, t+2, argv[3]);\n+\tbuf1 = get_tree_descriptor(r, t+0, base);\n+\tbuf2 = get_tree_descriptor(r, t+1, branch1);\n+\tbuf3 = get_tree_descriptor(r, t+2, branch2);\n \ttrivial_merge_trees(t, \"\");\n \tfree(buf1);\n \tfree(buf2);\n@@ -384,9 +385,74 @@ static int trivial_merge(int argc, const char **argv)\n \treturn 0;\n }\n \n+enum mode {\n+\tMODE_UNKNOWN,\n+\tMODE_TRIVIAL,\n+\tMODE_REAL,\n+};\n+\n+struct merge_tree_options {\n+\tint mode;\n+};\n+\n+static int real_merge(struct merge_tree_options *o,\n+\t\t      const char *branch1, const char *branch2)\n+{\n+\tdie(_(\"real merges are not yet implemented\"));\n+}\n+\n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 4)\n-\t\tusage(merge_tree_usage);\n-\treturn trivial_merge(argc, argv);\n+\tstruct merge_tree_options o = { 0 };\n+\tint expected_remaining_argc;\n+\n+\tconst char * const merge_tree_usage[] = {\n+\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n+\t\tNULL\n+\t};\n+\tstruct option mt_options[] = {\n+\t\tOPT_CMDMODE(0, \"write-tree\", &o.mode,\n+\t\t\t    N_(\"do a real merge instead of a trivial merge\"),\n+\t\t\t    MODE_REAL),\n+\t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n+\t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* Parse arguments */\n+\targc = parse_options(argc, argv, prefix, mt_options,\n+\t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n+\tswitch (o.mode) {\n+\tdefault:\n+\t\tBUG(\"unexpected command mode %d\", o.mode);\n+\tcase MODE_UNKNOWN:\n+\t\tswitch (argc) {\n+\t\tdefault:\n+\t\t\tusage_with_options(merge_tree_usage, mt_options);\n+\t\tcase 2:\n+\t\t\to.mode = MODE_REAL;\n+\t\t\tbreak;\n+\t\tcase 3:\n+\t\t\to.mode = MODE_TRIVIAL;\n+\t\t\tbreak;\n+\t\t}\n+\t\texpected_remaining_argc = argc;\n+\t\tbreak;\n+\tcase MODE_REAL:\n+\t\texpected_remaining_argc = 2;\n+\t\tbreak;\n+\tcase MODE_TRIVIAL:\n+\t\texpected_remaining_argc = 3;\n+\t\tbreak;\n+\t}\n+\n+\tif (argc != expected_remaining_argc)\n+\t\tusage_with_options(merge_tree_usage, mt_options);\n+\n+\t/* Do the relevant type of merge */\n+\tif (o.mode == MODE_REAL)\n+\t\treturn real_merge(&o, argv[0], argv[1]);\n+\telse\n+\t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/git.c b/git.c\nindex 5ff4f3e25b7..861d966c374 100644\n--- a/git.c\n+++ b/git.c\n@@ -565,7 +565,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-subtree\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-tree\", cmd_merge_tree, RUN_SETUP },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n \t{ \"mktree\", cmd_mktree, RUN_SETUP },\n \t{ \"multi-pack-index\", cmd_multi_pack_index, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"457472","messageId":"069af1ecc303b38b18f053c040416954097f2ee4.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 05/17] merge-ort: split out a separate display_update_messages() function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:48Z","receivedAt":"2022-06-18T00:21:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis patch includes no new code; it simply moves a bunch of lines into a\nnew function.  As such, there are no functional changes.  This is just a\npreparatory step to allow the printed messages to be handled differently\nby other callers, such as in `git merge-tree --write-tree`.\n\n(Patch best viewed with\n     --color-moved --color-moved-ws=allow-indentation-change\n to see that it is a simple code movement.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 78 ++++++++++++++++++++++++++++-------------------------\n merge-ort.h |  8 ++++++\n 2 files changed, 49 insertions(+), 37 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 0d3f42592fb..b9c9e906e94 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4256,6 +4256,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \treturn errs;\n }\n \n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result)\n+{\n+\tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n+\tint i;\n+\n+\tif (opt->record_conflict_msgs_as_headers)\n+\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n+\n+\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n+\n+\t/* Hack to pre-allocate olist to the desired size */\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\t\t   olist.alloc);\n+\n+\t/* Put every entry from output into olist, then sort */\n+\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\t\tstring_list_append(&olist, e->key)->util = e->value;\n+\t}\n+\tstring_list_sort(&olist);\n+\n+\t/* Iterate over the items, printing them */\n+\tfor (i = 0; i < olist.nr; ++i) {\n+\t\tstruct strbuf *sb = olist.items[i].util;\n+\n+\t\tprintf(\"%s\", sb->buf);\n+\t}\n+\tstring_list_clear(&olist, 0);\n+\n+\t/* Also include needed rename limit adjustment now */\n+\tdiff_warn_rename_limit(\"merge.renamelimit\",\n+\t\t\t       opti->renames.needed_limit, 0);\n+\n+\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\n@@ -4293,43 +4332,8 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\tfclose(fp);\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n-\n-\tif (display_update_msgs) {\n-\t\tstruct merge_options_internal *opti = result->priv;\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct strmap_entry *e;\n-\t\tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\t\tint i;\n-\n-\t\tif (opt->record_conflict_msgs_as_headers)\n-\t\t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n-\n-\t\ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n-\n-\t\t/* Hack to pre-allocate olist to the desired size */\n-\t\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n-\t\t\t   olist.alloc);\n-\n-\t\t/* Put every entry from output into olist, then sort */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstring_list_append(&olist, e->key)->util = e->value;\n-\t\t}\n-\t\tstring_list_sort(&olist);\n-\n-\t\t/* Iterate over the items, printing them */\n-\t\tfor (i = 0; i < olist.nr; ++i) {\n-\t\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\t\tprintf(\"%s\", sb->buf);\n-\t\t}\n-\t\tstring_list_clear(&olist, 0);\n-\n-\t\t/* Also include needed rename limit adjustment now */\n-\t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n-\t\t\t\t       opti->renames.needed_limit, 0);\n-\n-\t\ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n-\t}\n+\tif (display_update_msgs)\n+\t\tmerge_display_update_messages(opt, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex fe599b87868..e5aec45b18f 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    int update_worktree_and_index,\n \t\t\t    int display_update_msgs);\n \n+/*\n+ * Display messages about conflicts and which files were 3-way merged.\n+ * Automatically called by merge_switch_to_result() with stream == stdout,\n+ * so only call this when bypassing merge_switch_to_result().\n+ */\n+void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   struct merge_result *result);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"457473","messageId":"697470e50aeeb546e055e6c57b17aea32d9ea451.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 04/17] merge-tree: implement real merges","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:47Z","receivedAt":"2022-06-18T00:21:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds the ability to perform real merges rather than just trivial\nmerges (meaning handling three way content merges, recursive ancestor\nconsolidation, renames, proper directory/file conflict handling, and so\nforth).  However, unlike `git merge`, the working tree and index are\nleft alone and no branch is updated.\n\nThe only output is:\n  - the toplevel resulting tree printed on stdout\n  - exit status of 0 (clean), 1 (conflicts present), anything else\n    (merge could not be performed; unknown if clean or conflicted)\n\nThis output is meant to be used by some higher level script, perhaps in\na sequence of steps like this:\n\n   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n   test $? -eq 0 || die \"There were conflicts...\"\n   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n   git update-ref $BRANCH1 $NEWCOMMIT\n\nNote that higher level scripts may also want to access the\nconflict/warning messages normally output during a merge, or have quick\naccess to a list of files with conflicts.  That is not available in this\npreliminary implementation, but subsequent commits will add that\nability (meaning that NEWTREE would be a lot more than a tree in the\ncase of conflicts).\n\nThis also marks the traditional trivial merge of merge-tree as\ndeprecated.  The trivial merge not only had limited applicability, the\noutput format was also difficult to work with (and its format\nundocumented), and will generally be less performant than real merges.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  98 ++++++++++++++++++++++++----\n builtin/merge-tree.c             |  41 +++++++++++-\n t/t4301-merge-tree-write-tree.sh | 106 +++++++++++++++++++++++++++++++\n 3 files changed, 232 insertions(+), 13 deletions(-)\n create mode 100755 t/t4301-merge-tree-write-tree.sh\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 58731c19422..2a9c91328de 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -3,26 +3,100 @@ git-merge-tree(1)\n \n NAME\n ----\n-git-merge-tree - Show three-way merge without touching index\n+git-merge-tree - Perform merge without touching index or working tree\n \n \n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' <base-tree> <branch1> <branch2>\n+'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n+[[NEWMERGE]]\n DESCRIPTION\n -----------\n-Reads three tree-ish, and output trivial merge results and\n-conflicting stages to the standard output.  This is similar to\n-what three-way 'git read-tree -m' does, but instead of storing the\n-results in the index, the command outputs the entries to the\n-standard output.\n-\n-This is meant to be used by higher level scripts to compute\n-merge results outside of the index, and stuff the results back into the\n-index.  For this reason, the output from the command omits\n-entries that match the <branch1> tree.\n+\n+This command has a modern `--write-tree` mode and a deprecated\n+`--trivial-merge` mode.  With the exception of the\n+<<DEPMERGE,DEPRECATED DESCRIPTION>> section at the end, the rest of\n+this documentation describes modern `--write-tree` mode.\n+\n+Performs a merge, but does not make any new commits and does not read\n+from or write to either the working tree or index.\n+\n+The performed merge will use the same feature as the \"real\"\n+linkgit:git-merge[1], including:\n+\n+  * three way content merges of individual files\n+  * rename detection\n+  * proper directory/file conflict handling\n+  * recursive ancestor consolidation (i.e. when there is more than one\n+    merge base, creating a virtual merge base by merging the merge bases)\n+  * etc.\n+\n+After the merge completes, a new toplevel tree object is created.  See\n+`OUTPUT` below for details.\n+\n+[[OUTPUT]]\n+OUTPUT\n+------\n+\n+For either a successful or conflicted merge, the output from\n+git-merge-tree is simply one line:\n+\n+\t<OID of toplevel tree>\n+\n+The printed tree object corresponds to what would be checked out in\n+the working tree at the end of `git merge`, and thus may have files\n+with conflict markers in them.\n+\n+EXIT STATUS\n+-----------\n+\n+For a successful, non-conflicted merge, the exit status is 0.  When the\n+merge has conflicts, the exit status is 1.  If the merge is not able to\n+complete (or start) due to some kind of error, the exit status is\n+something other than 0 or 1 (and the output is unspecified).\n+\n+USAGE NOTES\n+-----------\n+\n+This command is intended as low-level plumbing, similar to\n+linkgit:git-hash-object[1], linkgit:git-mktree[1],\n+linkgit:git-commit-tree[1], linkgit:git-write-tree[1],\n+linkgit:git-update-ref[1], and linkgit:git-mktag[1].  Thus, it can be\n+used as a part of a series of steps such as:\n+\n+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)\n+       test $? -eq 0 || die \"There were conflicts...\"\n+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n+       git update-ref $BRANCH1 $NEWCOMMIT\n+\n+[[DEPMERGE]]\n+DEPRECATED DESCRIPTION\n+----------------------\n+\n+Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this\n+documentation, this section describes the deprecated `--trivial-merge`\n+mode.\n+\n+Other than the optional `--trivial-merge`, this mode accepts no\n+options.\n+\n+This mode reads three tree-ish, and outputs trivial merge results and\n+conflicting stages to the standard output in a semi-diff format.\n+Since this was designed for higher level scripts to consume and merge\n+the results back into the index, it omits entries that match\n+<branch1>.  The result of this second form is similar to what\n+three-way 'git read-tree -m' does, but instead of storing the results\n+in the index, the command outputs the entries to the standard output.\n+\n+This form not only has limited applicability (a trivial merge cannot\n+handle content merges of individual files, rename detection, proper\n+directory/file conflict handling, etc.), the output format is also\n+difficult to work with, and it will generally be less performant than\n+the first form even on successful merges (especially if working in\n+large repositories).\n \n GIT\n ---\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 0f9d928e862..2332525d8bd 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -2,6 +2,9 @@\n #include \"builtin.h\"\n #include \"tree-walk.h\"\n #include \"xdiff-interface.h\"\n+#include \"help.h\"\n+#include \"commit-reach.h\"\n+#include \"merge-ort.h\"\n #include \"object-store.h\"\n #include \"parse-options.h\"\n #include \"repository.h\"\n@@ -398,7 +401,43 @@ struct merge_tree_options {\n static int real_merge(struct merge_tree_options *o,\n \t\t      const char *branch1, const char *branch2)\n {\n-\tdie(_(\"real merges are not yet implemented\"));\n+\tstruct commit *parent1, *parent2;\n+\tstruct commit_list *merge_bases = NULL;\n+\tstruct merge_options opt;\n+\tstruct merge_result result = { 0 };\n+\n+\tparent1 = get_merge_parent(branch1);\n+\tif (!parent1)\n+\t\thelp_unknown_ref(branch1, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tparent2 = get_merge_parent(branch2);\n+\tif (!parent2)\n+\t\thelp_unknown_ref(branch2, \"merge-tree\",\n+\t\t\t\t _(\"not something we can merge\"));\n+\n+\tinit_merge_options(&opt, the_repository);\n+\n+\topt.show_rename_progress = 0;\n+\n+\topt.branch1 = branch1;\n+\topt.branch2 = branch2;\n+\n+\t/*\n+\t * Get the merge bases, in reverse order; see comment above\n+\t * merge_incore_recursive in merge-ort.h\n+\t */\n+\tmerge_bases = get_merge_bases(parent1, parent2);\n+\tif (!merge_bases)\n+\t\tdie(_(\"refusing to merge unrelated histories\"));\n+\tmerge_bases = reverse_commit_list(merge_bases);\n+\n+\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\tif (result.clean < 0)\n+\t\tdie(_(\"failure to merge\"));\n+\tputs(oid_to_hex(&result.tree->object.oid));\n+\tmerge_finalize(&opt, &result);\n+\treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nnew file mode 100755\nindex 00000000000..6d321652e21\n--- /dev/null\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -0,0 +1,106 @@\n+#!/bin/sh\n+\n+test_description='git merge-tree --write-tree'\n+\n+. ./test-lib.sh\n+\n+# This test is ort-specific\n+if test \"$GIT_TEST_MERGE_ALGORITHM\" != \"ort\"\n+then\n+\tskip_all=\"GIT_TEST_MERGE_ALGORITHM != ort\"\n+\ttest_done\n+fi\n+\n+test_expect_success setup '\n+\ttest_write_lines 1 2 3 4 5 >numbers &&\n+\techo hello >greeting &&\n+\techo foo >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\n+\tgit branch side1 &&\n+\tgit branch side2 &&\n+\tgit branch side3 &&\n+\n+\tgit checkout side1 &&\n+\ttest_write_lines 1 2 3 4 5 6 >numbers &&\n+\techo hi >greeting &&\n+\techo bar >whatever &&\n+\tgit add numbers greeting whatever &&\n+\ttest_tick &&\n+\tgit commit -m modify-stuff &&\n+\n+\tgit checkout side2 &&\n+\ttest_write_lines 0 1 2 3 4 5 >numbers &&\n+\techo yo >greeting &&\n+\tgit rm whatever &&\n+\tmkdir whatever &&\n+\t>whatever/empty &&\n+\tgit add numbers greeting whatever/empty &&\n+\ttest_tick &&\n+\tgit commit -m other-modifications &&\n+\n+\tgit checkout side3 &&\n+\tgit mv numbers sequence &&\n+\ttest_tick &&\n+\tgit commit -m rename-numbers\n+'\n+\n+test_expect_success 'Clean merge' '\n+\tTREE_OID=$(git merge-tree --write-tree side1 side3) &&\n+\tq_to_tab <<-EOF >expect &&\n+\t100644 blob $(git rev-parse side1:greeting)Qgreeting\n+\t100644 blob $(git rev-parse side1:numbers)Qsequence\n+\t100644 blob $(git rev-parse side1:whatever)Qwhatever\n+\tEOF\n+\n+\tgit ls-tree $TREE_OID >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Content merge and a few conflicts' '\n+\tgit checkout side1^0 &&\n+\ttest_must_fail git merge side2 &&\n+\texpected_tree=$(git rev-parse AUTO_MERGE) &&\n+\n+\t# We will redo the merge, while we are still in a conflicted state!\n+\ttest_when_finished \"git reset --hard\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n+\tactual_tree=$(head -n 1 RESULT) &&\n+\n+\t# Due to differences of e.g. \"HEAD\" vs \"side1\", the results will not\n+\t# exactly match.  Dig into individual files.\n+\n+\t# Numbers should have three-way merged cleanly\n+\ttest_write_lines 0 1 2 3 4 5 6 >expect &&\n+\tgit show ${actual_tree}:numbers >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# whatever and whatever~<branch> should have same HASHES\n+\tgit rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&\n+\tgit rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&\n+\ttest_cmp expect actual &&\n+\n+\t# greeting should have a merge conflict\n+\tgit show ${expected_tree}:greeting >tmp &&\n+\tsed -e s/HEAD/side1/ tmp >expect &&\n+\tgit show ${actual_tree}:greeting >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '\n+\t# Mis-spell with single \"s\" instead of double \"s\"\n+\ttest_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&\n+\n+\tgrep \"error: unknown option.*mesages\" expect\n+'\n+\n+test_expect_success 'Barf on too many arguments' '\n+\ttest_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect &&\n+\n+\tgrep \"^usage: git merge-tree\" expect\n+'\n+\n+test_done\n-- \ngitgitgadget\n\n"},{"id":"457474","messageId":"53c92a5d8d93c30305dddf8e2aa7a5e7fdfb493f.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 06/17] merge-tree: support including merge messages in output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:49Z","receivedAt":"2022-06-18T00:21:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen running `git merge-tree --write-tree`, we previously would only\nreturn an exit status reflecting the cleanness of a merge, and print out\nthe toplevel tree of the resulting merge.  Merges also have\ninformational messages, such as:\n  * \"Auto-merging <PATH>\"\n  * \"CONFLICT (content): ...\"\n  * \"CONFLICT (file/directory)\"\n  * etc.\nIn fact, when non-content conflicts occur (such as file/directory,\nmodify/delete, add/add with differing modes, rename/rename (1to2),\netc.), these informational messages may be the only notification the\nuser gets since these conflicts are not representable in the contents\nof the file.\n\nAdd a --[no-]messages option so that callers can request these messages\nbe included at the end of the output.  Include such messages by default\nwhen there are conflicts, and omit them by default when the merge is\nclean.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 47 ++++++++++++++++++++++++++++----\n builtin/merge-tree.c             | 21 ++++++++++++--\n t/t4301-merge-tree-write-tree.sh | 37 +++++++++++++++++++++++++\n 3 files changed, 97 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 2a9c91328de..25b462be14e 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree\n SYNOPSIS\n --------\n [verse]\n-'git merge-tree' [--write-tree] <branch1> <branch2>\n+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>\n 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)\n \n [[NEWMERGE]]\n@@ -37,18 +37,50 @@ linkgit:git-merge[1], including:\n After the merge completes, a new toplevel tree object is created.  See\n `OUTPUT` below for details.\n \n+OPTIONS\n+-------\n+\n+--[no-]messages::\n+\tWrite any informational messages such as \"Auto-merging <path>\"\n+\tor CONFLICT notices to the end of stdout.  If unspecified, the\n+\tdefault is to include these messages if there are merge\n+\tconflicts, and to omit them otherwise.\n+\n [[OUTPUT]]\n OUTPUT\n ------\n \n-For either a successful or conflicted merge, the output from\n-git-merge-tree is simply one line:\n+For a successful merge, the output from git-merge-tree is simply one\n+line:\n+\n+\t<OID of toplevel tree>\n+\n+Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Informational messages>\n+\n+These are discussed individually below.\n \n-The printed tree object corresponds to what would be checked out in\n-the working tree at the end of `git merge`, and thus may have files\n-with conflict markers in them.\n+[[OIDTLT]]\n+OID of toplevel tree\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a tree object that represents what would be checked out in the\n+working tree at the end of `git merge`.  If there were conflicts, then\n+files within this tree may have embedded conflict markers.\n+\n+[[IM]]\n+Informational messages\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+This always starts with a blank line to separate it from the previous\n+section, and then has free-form messages about the merge, such as:\n+\n+  * \"Auto-merging <file>\"\n+  * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n+  * \"Failed to merge submodule <submodule> (<reason>)\"\n+  * \"Warning: cannot merge binary files: <filename>\"\n \n EXIT STATUS\n -----------\n@@ -72,6 +104,9 @@ used as a part of a series of steps such as:\n        NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)\n        git update-ref $BRANCH1 $NEWCOMMIT\n \n+Note that when the exit status is non-zero, `NEWTREE` in this sequence\n+will contain a lot more output than just a tree.\n+\n [[DEPMERGE]]\n DEPRECATED DESCRIPTION\n ----------------------\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 2332525d8bd..831d9c77583 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -396,6 +396,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint show_messages;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -435,18 +436,27 @@ static int real_merge(struct merge_tree_options *o,\n \tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n \tif (result.clean < 0)\n \t\tdie(_(\"failure to merge\"));\n+\n+\tif (o->show_messages == -1)\n+\t\to->show_messages = !result.clean;\n+\n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (o->show_messages) {\n+\t\tprintf(\"\\n\");\n+\t\tmerge_display_update_messages(&opt, &result);\n+\t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\n }\n \n int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n {\n-\tstruct merge_tree_options o = { 0 };\n+\tstruct merge_tree_options o = { .show_messages = -1 };\n \tint expected_remaining_argc;\n+\tint original_argc;\n \n \tconst char * const merge_tree_usage[] = {\n-\t\tN_(\"git merge-tree [--write-tree] <branch1> <branch2>\"),\n+\t\tN_(\"git merge-tree [--write-tree] [<options>] <branch1> <branch2>\"),\n \t\tN_(\"git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>\"),\n \t\tNULL\n \t};\n@@ -456,10 +466,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    MODE_REAL),\n \t\tOPT_CMDMODE(0, \"trivial-merge\", &o.mode,\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n+\t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n+\t\t\t N_(\"also show informational/conflict messages\")),\n \t\tOPT_END()\n \t};\n \n \t/* Parse arguments */\n+\toriginal_argc = argc - 1; /* ignoring argv[0] */\n \targc = parse_options(argc, argv, prefix, mt_options,\n \t\t\t     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);\n \tswitch (o.mode) {\n@@ -483,8 +496,12 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\tbreak;\n \tcase MODE_TRIVIAL:\n \t\texpected_remaining_argc = 3;\n+\t\t/* Removal of `--trivial-merge` is expected */\n+\t\toriginal_argc--;\n \t\tbreak;\n \t}\n+\tif (o.mode == MODE_TRIVIAL && argc < original_argc)\n+\t\tdie(_(\"--trivial-merge is incompatible with all other options\"));\n \n \tif (argc != expected_remaining_argc)\n \t\tusage_with_options(merge_tree_usage, mt_options);\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 6d321652e21..719d81e7173 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -103,4 +103,41 @@ test_expect_success 'Barf on too many arguments' '\n \tgrep \"^usage: git merge-tree\" expect\n '\n \n+anonymize_hash() {\n+\tsed -e \"s/[0-9a-f]\\{40,\\}/HASH/g\" \"$@\"\n+}\n+\n+test_expect_success 'test conflict notices and such' '\n+\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"numbers\" should merge cleanly\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\tcat <<-EOF >expect &&\n+\tHASH\n+\n+\tAuto-merging greeting\n+\tCONFLICT (content): Merge conflict in greeting\n+\tAuto-merging numbers\n+\tCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.\n+\tCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n+for opt in $(git merge-tree --git-completion-helper-all)\n+do\n+\tif test $opt = \"--trivial-merge\" || test $opt = \"--write-tree\"\n+\tthen\n+\t\tcontinue\n+\tfi\n+\n+\ttest_expect_success \"usage: --trivial-merge is incompatible with $opt\" '\n+\t\ttest_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3\n+\t'\n+done\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"457475","messageId":"67a728d35f0e3a3dde63d7fc8116ed20cad44141.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 07/17] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:50Z","receivedAt":"2022-06-18T00:21:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAfter a merge, this function allows the user to extract the same\ninformation that would be printed by `ls-files -u`, which means\nfiles with their mode, oid, and stage.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 31 +++++++++++++++++++++++++++++++\n merge-ort.h | 21 +++++++++++++++++++++\n 2 files changed, 52 insertions(+)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex b9c9e906e94..1635d215c0b 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4295,6 +4295,37 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_leave(\"merge\", \"display messages\", opt->repo);\n }\n \n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\tstruct merge_options_internal *opti = result->priv;\n+\n+\tstrmap_for_each_entry(&opti->conflicted, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct conflict_info *ci = e->value;\n+\t\tint i;\n+\n+\t\tVERIFY_CI(ci);\n+\n+\t\tfor (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {\n+\t\t\tstruct stage_info *si;\n+\n+\t\t\tif (!(ci->filemask & (1ul << i)))\n+\t\t\t\tcontinue;\n+\n+\t\t\tsi = xmalloc(sizeof(*si));\n+\t\t\tsi->stage = i+1;\n+\t\t\tsi->mode = ci->stages[i].mode;\n+\t\t\toidcpy(&si->oid, &ci->stages[i].oid);\n+\t\t\tstring_list_append(conflicted_files, path)->util = si;\n+\t\t}\n+\t}\n+\t/* string_list_sort() uses a stable sort, so we're good */\n+\tstring_list_sort(conflicted_files);\n+}\n+\n void merge_switch_to_result(struct merge_options *opt,\n \t\t\t    struct tree *head,\n \t\t\t    struct merge_result *result,\ndiff --git a/merge-ort.h b/merge-ort.h\nindex e5aec45b18f..ddcc39d7270 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -2,6 +2,7 @@\n #define MERGE_ORT_H\n \n #include \"merge-recursive.h\"\n+#include \"hash.h\"\n \n struct commit;\n struct tree;\n@@ -88,6 +89,26 @@ void merge_switch_to_result(struct merge_options *opt,\n void merge_display_update_messages(struct merge_options *opt,\n \t\t\t\t   struct merge_result *result);\n \n+struct stage_info {\n+\tstruct object_id oid;\n+\tint mode;\n+\tint stage;\n+};\n+\n+/*\n+ * Provide a list of path -> {struct stage_info*} mappings for\n+ * all conflicted files.  Note that each path could appear up to three\n+ * times in the list, corresponding to 3 different stage entries.  In short,\n+ * this basically provides the info that would be printed by `ls-files -u`.\n+ *\n+ * result should have been populated by a call to\n+ * one of the merge_incore_[non]recursive() functions.\n+ *\n+ * conflicted_files should be empty before calling this function.\n+ */\n+void merge_get_conflicted_files(struct merge_result *result,\n+\t\t\t\tstruct string_list *conflicted_files);\n+\n /* Do needed cleanup when not calling merge_switch_to_result() */\n void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result);\n-- \ngitgitgadget\n\n"},{"id":"457476","messageId":"c92b81e7366f3c7b7fec1536ea2c41012b051bab.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 09/17] merge-tree: provide a list of which files have conflicts","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:52Z","receivedAt":"2022-06-18T00:21:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nCallers of `git merge-tree --write-tree` will often want to know which\nfiles had conflicts.  While they could potentially attempt to parse the\nCONFLICT notices printed, those messages are not meant to be machine\nreadable.  Provide a simpler mechanism of just printing the files (in\nthe same format as `git ls-files` with quoting, but restricted to\nunmerged files) in the output before the free-form messages.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  9 +++++++++\n builtin/merge-tree.c             | 26 +++++++++++++++++++++++---\n t/t4301-merge-tree-write-tree.sh | 11 +++++++++++\n 3 files changed, 43 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 25b462be14e..68a51c82618 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -58,6 +58,7 @@ line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n+\t<Conflicted file list>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -70,6 +71,14 @@ This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n+[[CFI]]\n+Conflicted file list\n+~~~~~~~~~~~~~~~~~~~~\n+\n+This is a sequence of lines containing a filename on each line, quoted\n+as explained for the configuration variable `core.quotePath` (see\n+linkgit:git-config[1]).\n+\n [[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 831d9c77583..13a9536f7c1 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -11,6 +11,9 @@\n #include \"blob.h\"\n #include \"exec-cmd.h\"\n #include \"merge-blobs.h\"\n+#include \"quote.h\"\n+\n+static int line_termination = '\\n';\n \n struct merge_list {\n \tstruct merge_list *next;\n@@ -400,7 +403,8 @@ struct merge_tree_options {\n };\n \n static int real_merge(struct merge_tree_options *o,\n-\t\t      const char *branch1, const char *branch2)\n+\t\t      const char *branch1, const char *branch2,\n+\t\t      const char *prefix)\n {\n \tstruct commit *parent1, *parent2;\n \tstruct commit_list *merge_bases = NULL;\n@@ -441,8 +445,24 @@ static int real_merge(struct merge_tree_options *o,\n \t\to->show_messages = !result.clean;\n \n \tputs(oid_to_hex(&result.tree->object.oid));\n+\tif (!result.clean) {\n+\t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n+\t\tconst char *last = NULL;\n+\t\tint i;\n+\n+\t\tmerge_get_conflicted_files(&result, &conflicted_files);\n+\t\tfor (i = 0; i < conflicted_files.nr; i++) {\n+\t\t\tconst char *name = conflicted_files.items[i].string;\n+\t\t\tif (last && !strcmp(last, name))\n+\t\t\t\tcontinue;\n+\t\t\twrite_name_quoted_relative(\n+\t\t\t\tname, prefix, stdout, line_termination);\n+\t\t\tlast = name;\n+\t\t}\n+\t\tstring_list_clear(&conflicted_files, 1);\n+\t}\n \tif (o->show_messages) {\n-\t\tprintf(\"\\n\");\n+\t\tputchar(line_termination);\n \t\tmerge_display_update_messages(&opt, &result);\n \t}\n \tmerge_finalize(&opt, &result);\n@@ -508,7 +528,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \n \t/* Do the relevant type of merge */\n \tif (o.mode == MODE_REAL)\n-\t\treturn real_merge(&o, argv[0], argv[1]);\n+\t\treturn real_merge(&o, argv[0], argv[1], prefix);\n \telse\n \t\treturn trivial_merge(argv[0], argv[1], argv[2]);\n }\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 719d81e7173..8e6dba44288 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -117,6 +117,8 @@ test_expect_success 'test conflict notices and such' '\n \t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n \tcat <<-EOF >expect &&\n \tHASH\n+\tgreeting\n+\twhatever~side1\n \n \tAuto-merging greeting\n \tCONFLICT (content): Merge conflict in greeting\n@@ -140,4 +142,13 @@ do\n \t'\n done\n \n+test_expect_success 'Just the conflicted files without the messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\ttest_write_lines HASH greeting whatever~side1 >expect &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"457477","messageId":"6419487e26bdaa3fdad357c993f5dd25efe1c70b.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 08/17] merge-ort: remove command-line-centric submodule message from merge-ort","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:51Z","receivedAt":"2022-06-18T00:21:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere was one case in merge-ort that would call path_msg() multiple\ntimes for the same logical conflict, and it was in order to give advice\nabout how to resolve a conflict.  This advice does not make as much\nsense with remerge-diff, or with merge-tree being invoked by a GitHub\nGUI for resolution of messages, and is making it hard to provide\nwhich-logical-conflict-affects-which-paths information in a machine\nparseable way to a higher level caller of merge-tree.  Let's simply\nremove this informational message.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c                | 9 +--------\n t/t6437-submodule-merge.sh | 2 +-\n 2 files changed, 2 insertions(+), 9 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 1635d215c0b..7e8b9cd6ea7 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -1693,15 +1693,8 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t      (struct commit *)merges.objects[0].item);\n \t\tpath_msg(opt, path, 0,\n \t\t\t _(\"Failed to merge submodule %s, but a possible merge \"\n-\t\t\t   \"resolution exists:\\n%s\\n\"),\n+\t\t\t   \"resolution exists: %s\"),\n \t\t\t path, sb.buf);\n-\t\tpath_msg(opt, path, 1,\n-\t\t\t _(\"If this is correct simply add it to the index \"\n-\t\t\t   \"for example\\n\"\n-\t\t\t   \"by using:\\n\\n\"\n-\t\t\t   \"  git update-index --cacheinfo 160000 %s \\\"%s\\\"\\n\\n\"\n-\t\t\t   \"which will accept this suggestion.\\n\"),\n-\t\t\t oid_to_hex(&merges.objects[0].item->oid), path);\n \t\tstrbuf_release(&sb);\n \t\tbreak;\n \tdefault:\ndiff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh\nindex 178413c22f0..c253bf759ab 100755\n--- a/t/t6437-submodule-merge.sh\n+++ b/t/t6437-submodule-merge.sh\n@@ -133,7 +133,7 @@ test_expect_success 'merging should conflict for non fast-forward' '\n \t(cd merge-search &&\n \t git checkout -b test-nonforward b &&\n \t (cd sub &&\n-\t  git rev-parse sub-d > ../expect) &&\n+\t  git rev-parse --short sub-d > ../expect) &&\n \t  if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n \t  then\n \t\ttest_must_fail git merge c >actual\n-- \ngitgitgadget\n\n"},{"id":"457478","messageId":"6b47c0fdbd7764275325ae75d534c506da4fe4d5.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 12/17] merge-ort: make `path_messages` a strmap to a string_list","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:55Z","receivedAt":"2022-06-18T00:21:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nThis allows us once again to get away with less data copying.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n diff.c      | 27 ++++++++++++++++++++-------\n merge-ort.c | 34 +---------------------------------\n merge-ort.h |  2 +-\n 3 files changed, 22 insertions(+), 41 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex e71cf758861..2214ae49e4b 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -3362,23 +3362,23 @@ struct userdiff_driver *get_textconv(struct repository *r,\n \treturn userdiff_get_textconv(r, one->driver);\n }\n \n-static struct strbuf *additional_headers(struct diff_options *o,\n-\t\t\t\t\t const char *path)\n+static struct string_list *additional_headers(struct diff_options *o,\n+\t\t\t\t\t      const char *path)\n {\n \tif (!o->additional_path_headers)\n \t\treturn NULL;\n \treturn strmap_get(o->additional_path_headers, path);\n }\n \n-static void add_formatted_headers(struct strbuf *msg,\n-\t\t\t\t  struct strbuf *more_headers,\n+static void add_formatted_header(struct strbuf *msg,\n+\t\t\t\t  const char *header,\n \t\t\t\t  const char *line_prefix,\n \t\t\t\t  const char *meta,\n \t\t\t\t  const char *reset)\n {\n-\tchar *next, *newline;\n+\tconst char *next, *newline;\n \n-\tfor (next = more_headers->buf; *next; next = newline) {\n+\tfor (next = header; *next; next = newline) {\n \t\tnewline = strchrnul(next, '\\n');\n \t\tstrbuf_addf(msg, \"%s%s%.*s%s\\n\", line_prefix, meta,\n \t\t\t    (int)(newline - next), next, reset);\n@@ -3387,6 +3387,19 @@ static void add_formatted_headers(struct strbuf *msg,\n \t}\n }\n \n+static void add_formatted_headers(struct strbuf *msg,\n+\t\t\t\t  struct string_list *more_headers,\n+\t\t\t\t  const char *line_prefix,\n+\t\t\t\t  const char *meta,\n+\t\t\t\t  const char *reset)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < more_headers->nr; i++)\n+\t\tadd_formatted_header(msg, more_headers->items[i].string,\n+\t\t\t\t     line_prefix, meta, reset);\n+}\n+\n static void builtin_diff(const char *name_a,\n \t\t\t const char *name_b,\n \t\t\t struct diff_filespec *one,\n@@ -4314,7 +4327,7 @@ static void fill_metainfo(struct strbuf *msg,\n \tconst char *set = diff_get_color(use_color, DIFF_METAINFO);\n \tconst char *reset = diff_get_color(use_color, DIFF_RESET);\n \tconst char *line_prefix = diff_line_prefix(o);\n-\tstruct strbuf *more_headers = NULL;\n+\tstruct string_list *more_headers = NULL;\n \n \t*must_show_header = 1;\n \tstrbuf_init(msg, PATH_MAX * 2 + 300);\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 668aec64f13..dfec08c88be 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4370,8 +4370,6 @@ void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result)\n {\n \tstruct merge_options_internal *opti = result->priv;\n-\tstruct hashmap_iter iter;\n-\tstruct strmap_entry *e;\n \n \tif (opt->renormalize)\n \t\tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n@@ -4379,15 +4377,6 @@ void merge_finalize(struct merge_options *opt,\n \n \tclear_or_reinit_internal_opts(opti, 0);\n \tFREE_AND_NULL(opti);\n-\n-\t/* Release and free each strbuf found in path_messages */\n-\tstrmap_for_each_entry(result->path_messages, &iter, e) {\n-\t\tstruct strbuf *buf = e->value;\n-\n-\t\tstrbuf_release(buf);\n-\t}\n-\tstrmap_clear(result->path_messages, 1);\n-\tFREE_AND_NULL(result->path_messages);\n }\n \n /*** Function Grouping: helper functions for merge_incore_*() ***/\n@@ -4611,8 +4600,6 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,\n \t\t\t\t\t    struct merge_result *result)\n {\n \tstruct object_id working_tree_oid;\n-\tstruct hashmap_iter iter;\n-\tstruct strmap_entry *e;\n \n \tif (opt->subtree_shift) {\n \t\tside2 = shift_tree_object(opt->repo, side1, side2,\n@@ -4653,26 +4640,7 @@ redo:\n \ttrace2_region_leave(\"merge\", \"process_entries\", opt->repo);\n \n \t/* Set return values */\n-\tresult->path_messages = xcalloc(1, sizeof(*result->path_messages));\n-\tstrmap_init_with_options(result->path_messages, NULL, 0);\n-\tstrmap_for_each_entry(&opt->priv->conflicts, &iter, e) {\n-\t\tconst char *path = e->key;\n-\t\tstruct strbuf *buf = strmap_get(result->path_messages, path);\n-\t\tstruct string_list *conflicts = e->value;\n-\n-\t\tif (!buf) {\n-\t\t\tbuf = xcalloc(1, sizeof(*buf));\n-\t\t\tstrbuf_init(buf, 0);\n-\t\t\tstrmap_put(result->path_messages, path, buf);\n-\t\t}\n-\n-\t\tfor (int i = 0; i < conflicts->nr; i++) {\n-\t\t\tif (buf->len)\n-\t\t\t\tstrbuf_addch(buf, '\\n');\n-\t\t\tstrbuf_addstr(buf, conflicts->items[i].string);\n-\t\t\tstrbuf_trim_trailing_newline(buf);\n-\t\t}\n-\t}\n+\tresult->path_messages = &opt->priv->conflicts;\n \n \tresult->tree = parse_tree_indirect(&working_tree_oid);\n \t/* existence of conflicted entries implies unclean */\ndiff --git a/merge-ort.h b/merge-ort.h\nindex f9c536ed8c4..c4909bcbf96 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -28,7 +28,7 @@ struct merge_result {\n \t/*\n \t * Special messages and conflict notices for various paths\n \t *\n-\t * This is a map of pathnames to strbufs. It contains various\n+\t * This is a map of pathnames to a string_list. It contains various\n \t * warning/conflict/notice messages (possibly multiple per path)\n \t * that callers may want to use.\n \t */\n-- \ngitgitgadget\n\n"},{"id":"457479","messageId":"b314aa9c436ae8e582f59ab4fb21238934a95d46.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 15/17] merge-tree: allow `ls-files -u` style info to be NUL terminated","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:58Z","receivedAt":"2022-06-18T00:21:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch as `git ls-files` has a -z option, let's add one to merge-tree so\nthat the conflict-info section can be NUL terminated (and avoid quoting\nof unusual filenames).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 21 +++++++++++++---\n builtin/merge-tree.c             |  4 ++-\n t/t4301-merge-tree-write-tree.sh | 42 ++++++++++++++++++++++++++++++++\n 3 files changed, 62 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex b89aabdb98e..75b57f8abab 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -40,6 +40,12 @@ After the merge completes, a new toplevel tree object is created.  See\n OPTIONS\n -------\n \n+-z::\n+\tDo not quote filenames in the <Conflicted file info> section,\n+\tand end each filename with a NUL character rather than\n+\tnewline.  Also begin the messages section with a NUL character\n+\tinstead of a newline.  See <<OUTPUT>> below for more information.\n+\n --name-only::\n \tIn the Conflicted file info section, instead of writing a list\n \tof (mode, oid, stage, path) tuples to output for conflicted\n@@ -76,7 +82,8 @@ OID of toplevel tree\n \n This is a tree object that represents what would be checked out in the\n working tree at the end of `git merge`.  If there were conflicts, then\n-files within this tree may have embedded conflict markers.\n+files within this tree may have embedded conflict markers.  This section\n+is always followed by a newline (or NUL if `-z` is passed).\n \n [[CFI]]\n Conflicted file info\n@@ -89,20 +96,26 @@ This is a sequence of lines with the format\n The filename will be quoted as explained for the configuration\n variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n the `--name-only` option is passed, the mode, object, and stage will\n-be omitted.\n+be omitted.  If `-z` is passed, the \"lines\" are terminated by a NUL\n+character instead of a newline character.\n \n [[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n-This always starts with a blank line to separate it from the previous\n-sections, and then has free-form messages about the merge, such as:\n+This always starts with a blank line (or NUL if `-z` is passed) to\n+separate it from the previous sections, and then has free-form\n+messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n   * \"Failed to merge submodule <submodule> (<reason>)\"\n   * \"Warning: cannot merge binary files: <filename>\"\n \n+Note that these free-form messages will never have a NUL character\n+in or between them, even if -z is passed.  It is simply a large block\n+of text taking up the remainder of the output.\n+\n EXIT STATUS\n -----------\n \ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex b3c5692498e..c159e317743 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -445,7 +445,7 @@ static int real_merge(struct merge_tree_options *o,\n \tif (o->show_messages == -1)\n \t\to->show_messages = !result.clean;\n \n-\tputs(oid_to_hex(&result.tree->object.oid));\n+\tprintf(\"%s%c\", oid_to_hex(&result.tree->object.oid), line_termination);\n \tif (!result.clean) {\n \t\tstruct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n \t\tconst char *last = NULL;\n@@ -494,6 +494,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_SET_INT('z', NULL, &line_termination,\n+\t\t\t    N_(\"separate paths with the NUL character\"), '\\0'),\n \t\tOPT_BOOL_F(0, \"name-only\",\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 0ec5f0d3f7e..88e75b18cc5 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -173,4 +173,46 @@ test_expect_success 'Check conflicted oids and modes without messages' '\n \ttest_cmp conflicted-file-info actual\n '\n \n+test_expect_success 'NUL terminated conflicted file \"lines\"' '\n+\tgit checkout -b tweak1 side1 &&\n+\ttest_write_lines zero 1 2 3 4 5 6 >numbers &&\n+\tgit add numbers &&\n+\tgit mv numbers \"Αυτά μου φαίνονται κινέζικα\" &&\n+\tgit commit -m \"Renamed numbers\" &&\n+\n+\ttest_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\tprintf \"\\\\n\" >>actual &&\n+\n+\t# Expected results:\n+\t#   \"greeting\" should merge with conflicts\n+\t#   \"whatever\" has *both* a modify/delete and a file/directory conflict\n+\t#   \"Αυτά μου φαίνονται κινέζικα\" should have a conflict\n+\techo HASH | lf_to_nul >expect &&\n+\n+\tq_to_tab <<-EOF | lf_to_nul >>expect &&\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~tweak1\n+\t100644 HASH 2Qwhatever~tweak1\n+\t100644 HASH 1QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 2QΑυτά μου φαίνονται κινέζικα\n+\t100644 HASH 3QΑυτά μου φαίνονται κινέζικα\n+\n+\tEOF\n+\n+\tq_to_nul <<-EOF >>expect &&\n+\t1QgreetingQAuto-mergingQAuto-merging greeting\n+\tQ1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting\n+\tQ2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n+\tQ1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n+\tQ1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα\n+\tQ1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n+\tQ\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"457480","messageId":"66df0c2e8379ca10debd403da295a2d6d230d453.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 16/17] merge-tree: add a --allow-unrelated-histories flag","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:59Z","receivedAt":"2022-06-18T00:21:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFolks may want to merge histories that have no common ancestry; provide\na flag with the same name as used by `git merge` to allow this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt |  5 +++++\n builtin/merge-tree.c             |  7 ++++++-\n t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-\n 3 files changed, 34 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 75b57f8abab..628324646d3 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -59,6 +59,11 @@ OPTIONS\n \tdefault is to include these messages if there are merge\n \tconflicts, and to omit them otherwise.\n \n+--allow-unrelated-histories::\n+\tmerge-tree will by default error out if the two branches specified\n+\tshare no common history.  This flag can be given to override that\n+\tcheck and make the merge proceed anyway.\n+\n [[OUTPUT]]\n OUTPUT\n ------\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex c159e317743..ae5782917b9 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -399,6 +399,7 @@ enum mode {\n \n struct merge_tree_options {\n \tint mode;\n+\tint allow_unrelated_histories;\n \tint show_messages;\n \tint name_only;\n };\n@@ -434,7 +435,7 @@ static int real_merge(struct merge_tree_options *o,\n \t * merge_incore_recursive in merge-ort.h\n \t */\n \tmerge_bases = get_merge_bases(parent1, parent2);\n-\tif (!merge_bases)\n+\tif (!merge_bases && !o->allow_unrelated_histories)\n \t\tdie(_(\"refusing to merge unrelated histories\"));\n \tmerge_bases = reverse_commit_list(merge_bases);\n \n@@ -500,6 +501,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t   &o.name_only,\n \t\t\t   N_(\"list filenames without modes/oids/stages\"),\n \t\t\t   PARSE_OPT_NONEG),\n+\t\tOPT_BOOL_F(0, \"allow-unrelated-histories\",\n+\t\t\t   &o.allow_unrelated_histories,\n+\t\t\t   N_(\"allow merging unrelated histories\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 88e75b18cc5..f091259a55e 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -44,7 +44,13 @@ test_expect_success setup '\n \tgit checkout side3 &&\n \tgit mv numbers sequence &&\n \ttest_tick &&\n-\tgit commit -m rename-numbers\n+\tgit commit -m rename-numbers &&\n+\n+\tgit switch --orphan unrelated &&\n+\t>something-else &&\n+\tgit add something-else &&\n+\ttest_tick &&\n+\tgit commit -m first-commit\n '\n \n test_expect_success 'Clean merge' '\n@@ -215,4 +221,20 @@ test_expect_success 'NUL terminated conflicted file \"lines\"' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'error out by default for unrelated histories' '\n+\ttest_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&\n+\n+\tgrep \"refusing to merge unrelated histories\" error\n+'\n+\n+test_expect_success 'can override merge of unrelated histories' '\n+\tgit merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&\n+\tTREE=$(cat tree) &&\n+\n+\tgit rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&\n+\tgit rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"457481","messageId":"70ea8281952c4ee75ce67fe907ce32340032e7e3.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 17/17] git-merge-tree.txt: add a section on potentional usage mistakes","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:21:00Z","receivedAt":"2022-06-18T00:21:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 53 ++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 628324646d3..d6c356740ef 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -156,6 +156,59 @@ with linkgit:git-merge[1]:\n   * any messages that would have been printed to stdout (the\n     <<IM,Informational messages>>)\n \n+MISTAKES TO AVOID\n+-----------------\n+\n+Do NOT look through the resulting toplevel tree to try to find which\n+files conflict; parse the <<CFI,Conflicted file info>> section instead.\n+Not only would parsing an entire tree be horrendously slow in large\n+repositories, there are numerous types of conflicts not representable by\n+conflict markers (modify/delete, mode conflict, binary file changed on\n+both sides, file/directory conflicts, various rename conflict\n+permutations, etc.)\n+\n+Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean\n+merge; check the exit status.  A merge can have conflicts without having\n+individual files conflict (there are a few types of directory rename\n+conflicts that fall into this category, and others might also be added\n+in the future).\n+\n+Do NOT attempt to guess or make the user guess the conflict types from\n+the <<CFI,Conflicted file info>> list.  The information there is\n+insufficient to do so.  For example: Rename/rename(1to2) conflicts (both\n+sides renamed the same file differently) will result in three different\n+file having higher order stages (but each only has one higher order\n+stage), with no way (short of the <<IM,Informational messages>> section)\n+to determine which three files are related.  File/directory conflicts\n+also result in a file with exactly one higher order stage.\n+Possibly-involved-in-directory-rename conflicts (when\n+\"merge.directoryRenames\" is unset or set to \"conflicts\") also result in\n+a file with exactly one higher order stage.  In all cases, the\n+<<IM,Informational messages>> section has the necessary info, though it\n+is not designed to be machine parseable.\n+\n+Do NOT assume that each paths from <<CFI,Conflicted file info>>, and\n+the logical conflicts in the <<IM,Informational messages>> have a\n+one-to-one mapping, nor that there is a one-to-many mapping, nor a\n+many-to-one mapping.  Many-to-many mappings exist, meaning that each\n+path can have many logical conflict types in a single merge, and each\n+logical conflict type can affect many paths.\n+\n+Do NOT assume all filenames listed in the <<IM,Informational messages>>\n+section had conflicts.  Messages can be included for files that have no\n+conflicts, such as \"Auto-merging <file>\".\n+\n+AVOID taking the OIDS from the <<CFI,Conflicted file info>> and\n+re-merging them to present the conflicts to the user.  This will lose\n+information.  Instead, look up the version of the file found within the\n+<<OIDTLT,OID of toplevel tree>> and show that instead.  In particular,\n+the latter will have conflict markers annotated with the original\n+branch/commit being merged and, if renames were involved, the original\n+filename.  While you could include the original branch/commit in the\n+conflict marker annotations when re-merging, the original filename is\n+not available from the <<CFI,Conflicted file info>> and thus you would\n+be losing information that might help the user resolve the conflict.\n+\n [[DEPMERGE]]\n DEPRECATED DESCRIPTION\n ----------------------\n-- \ngitgitgadget\n"},{"id":"457482","messageId":"662e97f2ed4b6a0698bb86493efc3650763600d3.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 14/17] merge-ort: optionally produce machine-readable output","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:57Z","receivedAt":"2022-06-18T00:21:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWith the new `detailed` parameter, a new mode can be triggered when\ndisplaying the merge messages: The `detailed` mode prints NUL-delimited\nfields of the following form:\n\n\t<path-count> NUL <path>... NUL <conflict-type> NUL <message>\n\nThe `<path-count>` field determines how many `<path>` fields there are.\n\nThe intention of this mode is to support server-side operations, where\nworktree-less merges can lead to conflicts and depending on the type\nand/or path count, the caller might know how to handle said conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/merge-tree.c |  3 ++-\n merge-ort.c          | 22 ++++++++++++++++++++--\n merge-ort.h          |  1 +\n 3 files changed, 23 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex c61b5b4a10d..b3c5692498e 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -468,7 +468,8 @@ static int real_merge(struct merge_tree_options *o,\n \t}\n \tif (o->show_messages) {\n \t\tputchar(line_termination);\n-\t\tmerge_display_update_messages(&opt, &result);\n+\t\tmerge_display_update_messages(&opt, line_termination == '\\0',\n+\t\t\t\t\t      &result);\n \t}\n \tmerge_finalize(&opt, &result);\n \treturn !result.clean; /* result.clean < 0 handled above */\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 432937255f6..4a56c189ddf 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -4412,6 +4412,7 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n }\n \n void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   int detailed,\n \t\t\t\t   struct merge_result *result)\n {\n \tstruct merge_options_internal *opti = result->priv;\n@@ -4437,8 +4438,25 @@ void merge_display_update_messages(struct merge_options *opt,\n \t/* Iterate over the items, printing them */\n \tfor (int path_nr = 0; path_nr < olist.nr; ++path_nr) {\n \t\tstruct string_list *conflicts = olist.items[path_nr].util;\n-\t\tfor (int i = 0; i < conflicts->nr; i++)\n+\t\tfor (int i = 0; i < conflicts->nr; i++) {\n+\t\t\tstruct logical_conflict_info *info =\n+\t\t\t\tconflicts->items[i].util;\n+\n+\t\t\tif (detailed) {\n+\t\t\t\tprintf(\"%lu\", (unsigned long)info->paths.nr);\n+\t\t\t\tputchar('\\0');\n+\t\t\t\tfor (int n = 0; n < info->paths.nr; n++) {\n+\t\t\t\t\tfputs(info->paths.v[n], stdout);\n+\t\t\t\t\tputchar('\\0');\n+\t\t\t\t}\n+\t\t\t\tfputs(type_short_descriptions[info->type],\n+\t\t\t\t      stdout);\n+\t\t\t\tputchar('\\0');\n+\t\t\t}\n \t\t\tputs(conflicts->items[i].string);\n+\t\t\tif (detailed)\n+\t\t\t\tputchar('\\0');\n+\t\t}\n \t}\n \tstring_list_clear(&olist, 0);\n \n@@ -4518,7 +4536,7 @@ void merge_switch_to_result(struct merge_options *opt,\n \t\ttrace2_region_leave(\"merge\", \"write_auto_merge\", opt->repo);\n \t}\n \tif (display_update_msgs)\n-\t\tmerge_display_update_messages(opt, result);\n+\t\tmerge_display_update_messages(opt, /* detailed */ 0, result);\n \n \tmerge_finalize(opt, result);\n }\ndiff --git a/merge-ort.h b/merge-ort.h\nindex c4909bcbf96..a994c9a5fcd 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -87,6 +87,7 @@ void merge_switch_to_result(struct merge_options *opt,\n  * so only call this when bypassing merge_switch_to_result().\n  */\n void merge_display_update_messages(struct merge_options *opt,\n+\t\t\t\t   int detailed,\n \t\t\t\t   struct merge_result *result);\n \n struct stage_info {\n-- \ngitgitgadget\n\n"},{"id":"457483","messageId":"7eb70f77c81bd506c6d6be680961677407bf68df.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 13/17] merge-ort: store more specific conflict information","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:56Z","receivedAt":"2022-06-18T00:21:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nIt is all fine and dandy for a regular Git command that is intended to\nbe run interactively to produce a bunch of messages upon an error.\n\nHowever, in `merge-ort`'s case, we want to call the command e.g. in\nserver-side software, where the actual error messages are not quite as\ninteresting as machine-readable, immutable terms that describe the exact\nnature of any given conflict.\n\nWith this patch, the `merge-ort` machinery records the exact type (as\nspecified via an `enum` value) as well as the involved path(s) together\nwith the conflict's message.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n merge-ort.c | 267 +++++++++++++++++++++++++++++++++++++++++-----------\n 1 file changed, 212 insertions(+), 55 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex dfec08c88be..432937255f6 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -483,6 +483,100 @@ struct conflict_info {\n \tunsigned match_mask:3;\n };\n \n+enum conflict_and_info_types {\n+\t/* \"Simple\" conflicts and informational messages */\n+\tINFO_AUTO_MERGING = 0,\n+\tCONFLICT_CONTENTS,       /* text file that failed to merge */\n+\tCONFLICT_BINARY,\n+\tCONFLICT_FILE_DIRECTORY,\n+\tCONFLICT_DISTINCT_MODES,\n+\tCONFLICT_MODIFY_DELETE,\n+\tCONFLICT_PRESENT_DESPITE_SKIPPED,\n+\n+\t/* Regular rename */\n+\tCONFLICT_RENAME_RENAME,   /* same file renamed differently */\n+\tCONFLICT_RENAME_COLLIDES, /* rename/add or two files renamed to 1 */\n+\tCONFLICT_RENAME_DELETE,\n+\n+\t/* Basic directory rename */\n+\tCONFLICT_DIR_RENAME_SUGGESTED,\n+\tINFO_DIR_RENAME_APPLIED,\n+\n+\t/* Special directory rename cases */\n+\tINFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME,\n+\tCONFLICT_DIR_RENAME_FILE_IN_WAY,\n+\tCONFLICT_DIR_RENAME_COLLISION,\n+\tCONFLICT_DIR_RENAME_SPLIT,\n+\n+\t/* Basic submodule */\n+\tINFO_SUBMODULE_FAST_FORWARDING,\n+\tCONFLICT_SUBMODULE_FAILED_TO_MERGE,\n+\n+\t/* Special submodule cases broken out from FAILED_TO_MERGE */\n+\tCONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION,\n+\tCONFLICT_SUBMODULE_NOT_INITIALIZED,\n+\tCONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE,\n+\tCONFLICT_SUBMODULE_MAY_HAVE_REWINDS,\n+\n+\t/* Keep this entry _last_ in the list */\n+\tNB_CONFLICT_TYPES,\n+};\n+\n+/*\n+ * Short description of conflict type, relied upon by external tools.\n+ *\n+ * We can add more entries, but DO NOT change any of these strings.  Also,\n+ * Order MUST match conflict_info_and_types.\n+ */\n+static const char *type_short_descriptions[] = {\n+\t/*** \"Simple\" conflicts and informational messages ***/\n+\t[INFO_AUTO_MERGING] = \"Auto-merging\",\n+\t[CONFLICT_CONTENTS] = \"CONFLICT (contents)\",\n+\t[CONFLICT_BINARY] = \"CONFLICT (binary)\",\n+\t[CONFLICT_FILE_DIRECTORY] = \"CONFLICT (file/directory)\",\n+\t[CONFLICT_DISTINCT_MODES] = \"CONFLICT (distinct modes)\",\n+\t[CONFLICT_MODIFY_DELETE] = \"CONFLICT (modify/delete)\",\n+\t[CONFLICT_PRESENT_DESPITE_SKIPPED] =\n+\t\t\"CONFLICT (upgrade your version of git)\",\n+\n+\t/*** Regular rename ***/\n+\t[CONFLICT_RENAME_RENAME] = \"CONFLICT (rename/rename)\",\n+\t[CONFLICT_RENAME_COLLIDES] = \"CONFLICT (rename involved in collision)\",\n+\t[CONFLICT_RENAME_DELETE] = \"CONFLICT (rename/delete)\",\n+\n+\t/*** Basic directory rename ***/\n+\t[CONFLICT_DIR_RENAME_SUGGESTED] =\n+\t\t\"CONFLICT (directory rename suggested)\",\n+\t[INFO_DIR_RENAME_APPLIED] = \"Path updated due to directory rename\",\n+\n+\t/*** Special directory rename cases ***/\n+\t[INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME] =\n+\t\t\"Directory rename skipped since directory was renamed on both sides\",\n+\t[CONFLICT_DIR_RENAME_FILE_IN_WAY] =\n+\t\t\"CONFLICT (file in way of directory rename)\",\n+\t[CONFLICT_DIR_RENAME_COLLISION] = \"CONFLICT(directory rename collision)\",\n+\t[CONFLICT_DIR_RENAME_SPLIT] = \"CONFLICT(directory rename unclear split)\",\n+\n+\t/*** Basic submodule ***/\n+\t[INFO_SUBMODULE_FAST_FORWARDING] = \"Fast forwarding submodule\",\n+\t[CONFLICT_SUBMODULE_FAILED_TO_MERGE] = \"CONFLICT (submodule)\",\n+\n+\t/*** Special submodule cases broken out from FAILED_TO_MERGE ***/\n+\t[CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION] =\n+\t\t\"CONFLICT (submodule with possible resolution)\",\n+\t[CONFLICT_SUBMODULE_NOT_INITIALIZED] =\n+\t\t\"CONFLICT (submodule not initialized)\",\n+\t[CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE] =\n+\t\t\"CONFLICT (submodule history not available)\",\n+\t[CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] =\n+\t\t\"CONFLICT (submodule may have rewinds)\",\n+};\n+\n+struct logical_conflict_info {\n+\tenum conflict_and_info_types type;\n+\tstruct strvec paths;\n+};\n+\n /*** Function Grouping: various utility functions ***/\n \n /*\n@@ -571,6 +665,11 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \t\t/* Release and free each strbuf found in output */\n \t\tstrmap_for_each_entry(&opti->conflicts, &iter, e) {\n \t\t\tstruct string_list *list = e->value;\n+\t\t\tfor (int i = 0; i < list->nr; i++) {\n+\t\t\t\tstruct logical_conflict_info *info =\n+\t\t\t\t\tlist->items[i].util;\n+\t\t\t\tstrvec_clear(&info->paths);\n+\t\t\t}\n \t\t\t/*\n \t\t\t * While strictly speaking we don't need to\n \t\t\t * free(conflicts) here because we could pass\n@@ -629,31 +728,56 @@ static void format_commit(struct strbuf *sb,\n \tstrbuf_addch(sb, '\\n');\n }\n \n-__attribute__((format (printf, 4, 5)))\n+__attribute__((format (printf, 8, 9)))\n static void path_msg(struct merge_options *opt,\n-\t\t     const char *path,\n+\t\t     enum conflict_and_info_types type,\n \t\t     int omittable_hint, /* skippable under --remerge-diff */\n+\t\t     const char *primary_path,\n+\t\t     const char *other_path_1, /* may be NULL */\n+\t\t     const char *other_path_2, /* may be NULL */\n+\t\t     struct string_list *other_paths, /* may be NULL */\n \t\t     const char *fmt, ...)\n {\n \tva_list ap;\n \tstruct string_list *path_conflicts;\n+\tstruct logical_conflict_info *info;\n \tstruct strbuf buf = STRBUF_INIT;\n \tstruct strbuf *dest;\n \tstruct strbuf tmp = STRBUF_INIT;\n \n+\t/* Sanity checks */\n+\tassert(omittable_hint ==\n+\t       !starts_with(type_short_descriptions[type], \"CONFLICT\") ||\n+\t       type == CONFLICT_DIR_RENAME_SUGGESTED ||\n+\t       type == CONFLICT_PRESENT_DESPITE_SKIPPED);\n \tif (opt->record_conflict_msgs_as_headers && omittable_hint)\n \t\treturn; /* Do not record mere hints in headers */\n \tif (opt->priv->call_depth && opt->verbosity < 5)\n \t\treturn; /* Ignore messages from inner merges */\n \n \t/* Ensure path_conflicts (ptr to array of logical_conflict) allocated */\n-\tpath_conflicts = strmap_get(&opt->priv->conflicts, path);\n+\tpath_conflicts = strmap_get(&opt->priv->conflicts, primary_path);\n \tif (!path_conflicts) {\n \t\tpath_conflicts = xmalloc(sizeof(*path_conflicts));\n \t\tstring_list_init_dup(path_conflicts);\n-\t\tstrmap_put(&opt->priv->conflicts, path, path_conflicts);\n+\t\tstrmap_put(&opt->priv->conflicts, primary_path, path_conflicts);\n \t}\n \n+\t/* Add a logical_conflict at the end to store info from this call */\n+\tinfo = xcalloc(1, sizeof(*info));\n+\tinfo->type = type;\n+\tstrvec_init(&info->paths);\n+\n+\t/* Handle the list of paths */\n+\tstrvec_push(&info->paths, primary_path);\n+\tif (other_path_1)\n+\t\tstrvec_push(&info->paths, other_path_1);\n+\tif (other_path_2)\n+\t\tstrvec_push(&info->paths, other_path_2);\n+\tif (other_paths)\n+\t\tfor (int i = 0; i < other_paths->nr; i++)\n+\t\tstrvec_push(&info->paths, other_paths->items[i].string);\n+\n \t/* Handle message and its format, in normal case */\n \tdest = (opt->record_conflict_msgs_as_headers ? &tmp : &buf);\n \n@@ -690,7 +814,8 @@ static void path_msg(struct merge_options *opt,\n \n \t\tstrbuf_release(&tmp);\n \t}\n-\tstring_list_append_nodup(path_conflicts, strbuf_detach(&buf, NULL));\n+\tstring_list_append_nodup(path_conflicts, strbuf_detach(&buf, NULL))\n+\t\t->util = info;\n }\n \n static struct diff_filespec *pool_alloc_filespec(struct mem_pool *pool,\n@@ -1631,16 +1756,18 @@ static int merge_submodule(struct merge_options *opt,\n \t\treturn 0;\n \n \tif (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {\n-\t\tpath_msg(opt, path, 0,\n-\t\t\t\t_(\"Failed to merge submodule %s (not checked out)\"),\n-\t\t\t\tpath);\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s (not checked out)\"),\n+\t\t\t path);\n \t\treturn 0;\n \t}\n \n \tif (!(commit_o = lookup_commit_reference(&subrepo, o)) ||\n \t    !(commit_a = lookup_commit_reference(&subrepo, a)) ||\n \t    !(commit_b = lookup_commit_reference(&subrepo, b))) {\n-\t\tpath_msg(opt, path, 0,\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, 0,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s (commits not present)\"),\n \t\t\t path);\n \t\tgoto cleanup;\n@@ -1649,7 +1776,8 @@ static int merge_submodule(struct merge_options *opt,\n \t/* check whether both changes are forward */\n \tif (!repo_in_merge_bases(&subrepo, commit_o, commit_a) ||\n \t    !repo_in_merge_bases(&subrepo, commit_o, commit_b)) {\n-\t\tpath_msg(opt, path, 0,\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s \"\n \t\t\t   \"(commits don't follow merge-base)\"),\n \t\t\t path);\n@@ -1659,7 +1787,8 @@ static int merge_submodule(struct merge_options *opt,\n \t/* Case #1: a is contained in b or vice versa */\n \tif (repo_in_merge_bases(&subrepo, commit_a, commit_b)) {\n \t\toidcpy(result, b);\n-\t\tpath_msg(opt, path, 1,\n+\t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Note: Fast-forwarding submodule %s to %s\"),\n \t\t\t path, oid_to_hex(b));\n \t\tret = 1;\n@@ -1667,7 +1796,8 @@ static int merge_submodule(struct merge_options *opt,\n \t}\n \tif (repo_in_merge_bases(&subrepo, commit_b, commit_a)) {\n \t\toidcpy(result, a);\n-\t\tpath_msg(opt, path, 1,\n+\t\tpath_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Note: Fast-forwarding submodule %s to %s\"),\n \t\t\t path, oid_to_hex(a));\n \t\tret = 1;\n@@ -1690,13 +1820,16 @@ static int merge_submodule(struct merge_options *opt,\n \t\t\t\t\t &merges);\n \tswitch (parent_count) {\n \tcase 0:\n-\t\tpath_msg(opt, path, 0, _(\"Failed to merge submodule %s\"), path);\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0,\n+\t\t\t path, NULL, NULL, NULL,\n+\t\t\t _(\"Failed to merge submodule %s\"), path);\n \t\tbreak;\n \n \tcase 1:\n \t\tformat_commit(&sb, 4, &subrepo,\n \t\t\t      (struct commit *)merges.objects[0].item);\n-\t\tpath_msg(opt, path, 0,\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, 0,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s, but a possible merge \"\n \t\t\t   \"resolution exists: %s\"),\n \t\t\t path, sb.buf);\n@@ -1706,7 +1839,8 @@ static int merge_submodule(struct merge_options *opt,\n \t\tfor (i = 0; i < merges.nr; i++)\n \t\t\tformat_commit(&sb, 4, &subrepo,\n \t\t\t\t      (struct commit *)merges.objects[i].item);\n-\t\tpath_msg(opt, path, 0,\n+\t\tpath_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, 0,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t _(\"Failed to merge submodule %s, but multiple \"\n \t\t\t   \"possible merges exist:\\n%s\"), path, sb.buf);\n \t\tstrbuf_release(&sb);\n@@ -1832,7 +1966,8 @@ static int merge_3way(struct merge_options *opt,\n \t\t\t\t&src1, name1, &src2, name2,\n \t\t\t\t&opt->priv->attr_index, &ll_opts);\n \tif (merge_status == LL_MERGE_BINARY_CONFLICT)\n-\t\tpath_msg(opt, path, 0,\n+\t\tpath_msg(opt, CONFLICT_BINARY, 0,\n+\t\t\t path, NULL, NULL, NULL,\n \t\t\t \"warning: Cannot merge binary files: %s (%s vs. %s)\",\n \t\t\t path, name1, name2);\n \n@@ -1944,7 +2079,8 @@ static int handle_content_merge(struct merge_options *opt,\n \t\tif (ret)\n \t\t\treturn -1;\n \t\tclean &= (merge_status == 0);\n-\t\tpath_msg(opt, path, 1, _(\"Auto-merging %s\"), path);\n+\t\tpath_msg(opt, INFO_AUTO_MERGING, 1, path, NULL, NULL, NULL,\n+\t\t\t _(\"Auto-merging %s\"), path);\n \t} else if (S_ISGITLINK(a->mode)) {\n \t\tint two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));\n \t\tclean = merge_submodule(opt, pathnames[0],\n@@ -2082,21 +2218,24 @@ static char *handle_path_level_conflicts(struct merge_options *opt,\n \t\tc_info->reported_already = 1;\n \t\tstrbuf_add_separated_string_list(&collision_paths, \", \",\n \t\t\t\t\t\t &c_info->source_files);\n-\t\tpath_msg(opt, new_path, 0,\n-\t\t\t _(\"CONFLICT (implicit dir rename): Existing file/dir \"\n-\t\t\t   \"at %s in the way of implicit directory rename(s) \"\n-\t\t\t   \"putting the following path(s) there: %s.\"),\n-\t\t       new_path, collision_paths.buf);\n+\t\tpath_msg(opt, CONFLICT_DIR_RENAME_FILE_IN_WAY, 0,\n+\t\t\t new_path, NULL, NULL, &c_info->source_files,\n+\t\t\t _(\"CONFLICT (implicit dir rename): Existing \"\n+\t\t\t   \"file/dir at %s in the way of implicit \"\n+\t\t\t   \"directory rename(s) putting the following \"\n+\t\t\t   \"path(s) there: %s.\"),\n+\t\t\t new_path, collision_paths.buf);\n \t\tclean = 0;\n \t} else if (c_info->source_files.nr > 1) {\n \t\tc_info->reported_already = 1;\n \t\tstrbuf_add_separated_string_list(&collision_paths, \", \",\n \t\t\t\t\t\t &c_info->source_files);\n-\t\tpath_msg(opt, new_path, 0,\n-\t\t\t _(\"CONFLICT (implicit dir rename): Cannot map more \"\n-\t\t\t   \"than one path to %s; implicit directory renames \"\n-\t\t\t   \"tried to put these paths there: %s\"),\n-\t\t       new_path, collision_paths.buf);\n+\t\tpath_msg(opt, CONFLICT_DIR_RENAME_COLLISION, 0,\n+\t\t\t new_path, NULL, NULL, &c_info->source_files,\n+\t\t\t _(\"CONFLICT (implicit dir rename): Cannot map \"\n+\t\t\t   \"more than one path to %s; implicit directory \"\n+\t\t\t   \"renames tried to put these paths there: %s\"),\n+\t\t\t new_path, collision_paths.buf);\n \t\tclean = 0;\n \t}\n \n@@ -2150,13 +2289,14 @@ static void get_provisional_directory_renames(struct merge_options *opt,\n \t\t\tcontinue;\n \n \t\tif (bad_max == max) {\n-\t\t\tpath_msg(opt, source_dir, 0,\n-\t\t\t       _(\"CONFLICT (directory rename split): \"\n-\t\t\t\t \"Unclear where to rename %s to; it was \"\n-\t\t\t\t \"renamed to multiple other directories, with \"\n-\t\t\t\t \"no destination getting a majority of the \"\n-\t\t\t\t \"files.\"),\n-\t\t\t       source_dir);\n+\t\t\tpath_msg(opt, CONFLICT_DIR_RENAME_SPLIT, 0,\n+\t\t\t\t source_dir, NULL, NULL, NULL,\n+\t\t\t\t _(\"CONFLICT (directory rename split): \"\n+\t\t\t\t   \"Unclear where to rename %s to; it was \"\n+\t\t\t\t   \"renamed to multiple other directories, \"\n+\t\t\t\t   \"with no destination getting a majority of \"\n+\t\t\t\t   \"the files.\"),\n+\t\t\t\t source_dir);\n \t\t\t*clean = 0;\n \t\t} else {\n \t\t\tstrmap_put(&renames->dir_renames[side],\n@@ -2304,7 +2444,8 @@ static char *check_for_directory_rename(struct merge_options *opt,\n \t */\n \totherinfo = strmap_get_entry(dir_rename_exclusions, new_dir);\n \tif (otherinfo) {\n-\t\tpath_msg(opt, rename_info->key, 1,\n+\t\tpath_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1,\n+\t\t\t rename_info->key, path, new_dir, NULL,\n \t\t\t _(\"WARNING: Avoiding applying %s -> %s rename \"\n \t\t\t   \"to %s, because %s itself was renamed.\"),\n \t\t\t rename_info->key, new_dir, path, new_dir);\n@@ -2444,14 +2585,16 @@ static void apply_directory_rename_modifications(struct merge_options *opt,\n \tif (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) {\n \t\t/* Notify user of updated path */\n \t\tif (pair->status == 'A')\n-\t\t\tpath_msg(opt, new_path, 1,\n+\t\t\tpath_msg(opt, INFO_DIR_RENAME_APPLIED, 1,\n+\t\t\t\t new_path, old_path, NULL, NULL,\n \t\t\t\t _(\"Path updated: %s added in %s inside a \"\n \t\t\t\t   \"directory that was renamed in %s; moving \"\n \t\t\t\t   \"it to %s.\"),\n \t\t\t\t old_path, branch_with_new_path,\n \t\t\t\t branch_with_dir_rename, new_path);\n \t\telse\n-\t\t\tpath_msg(opt, new_path, 1,\n+\t\t\tpath_msg(opt, INFO_DIR_RENAME_APPLIED, 1,\n+\t\t\t\t new_path, old_path, NULL, NULL,\n \t\t\t\t _(\"Path updated: %s renamed to %s in %s, \"\n \t\t\t\t   \"inside a directory that was renamed in %s; \"\n \t\t\t\t   \"moving it to %s.\"),\n@@ -2464,7 +2607,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt,\n \t\t */\n \t\tci->path_conflict = 1;\n \t\tif (pair->status == 'A')\n-\t\t\tpath_msg(opt, new_path, 1,\n+\t\t\tpath_msg(opt, CONFLICT_DIR_RENAME_SUGGESTED, 1,\n+\t\t\t\t new_path, old_path, NULL, NULL,\n \t\t\t\t _(\"CONFLICT (file location): %s added in %s \"\n \t\t\t\t   \"inside a directory that was renamed in %s, \"\n \t\t\t\t   \"suggesting it should perhaps be moved to \"\n@@ -2472,7 +2616,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt,\n \t\t\t\t old_path, branch_with_new_path,\n \t\t\t\t branch_with_dir_rename, new_path);\n \t\telse\n-\t\t\tpath_msg(opt, new_path, 1,\n+\t\t\tpath_msg(opt, CONFLICT_DIR_RENAME_SUGGESTED, 1,\n+\t\t\t\t new_path, old_path, NULL, NULL,\n \t\t\t\t _(\"CONFLICT (file location): %s renamed to %s \"\n \t\t\t\t   \"in %s, inside a directory that was renamed \"\n \t\t\t\t   \"in %s, suggesting it should perhaps be \"\n@@ -2628,7 +2773,8 @@ static int process_renames(struct merge_options *opt,\n \t\t\t * and remove the setting of base->path_conflict to 1.\n \t\t\t */\n \t\t\tbase->path_conflict = 1;\n-\t\t\tpath_msg(opt, oldpath, 0,\n+\t\t\tpath_msg(opt, CONFLICT_RENAME_RENAME, 0,\n+\t\t\t\t pathnames[0], pathnames[1], pathnames[2], NULL,\n \t\t\t\t _(\"CONFLICT (rename/rename): %s renamed to \"\n \t\t\t\t   \"%s in %s and to %s in %s.\"),\n \t\t\t\t pathnames[0],\n@@ -2723,7 +2869,8 @@ static int process_renames(struct merge_options *opt,\n \t\t\tmemcpy(&newinfo->stages[target_index], &merged,\n \t\t\t       sizeof(merged));\n \t\t\tif (!clean) {\n-\t\t\t\tpath_msg(opt, newpath, 0,\n+\t\t\t\tpath_msg(opt, CONFLICT_RENAME_COLLIDES, 0,\n+\t\t\t\t\t newpath, oldpath, NULL, NULL,\n \t\t\t\t\t _(\"CONFLICT (rename involved in \"\n \t\t\t\t\t   \"collision): rename of %s -> %s has \"\n \t\t\t\t\t   \"content conflicts AND collides \"\n@@ -2742,7 +2889,8 @@ static int process_renames(struct merge_options *opt,\n \t\t\t */\n \n \t\t\tnewinfo->path_conflict = 1;\n-\t\t\tpath_msg(opt, newpath, 0,\n+\t\t\tpath_msg(opt, CONFLICT_RENAME_DELETE, 0,\n+\t\t\t\t newpath, oldpath, NULL, NULL,\n \t\t\t\t _(\"CONFLICT (rename/delete): %s renamed \"\n \t\t\t\t   \"to %s in %s, but deleted in %s.\"),\n \t\t\t\t oldpath, newpath, rename_branch, delete_branch);\n@@ -2766,7 +2914,8 @@ static int process_renames(struct merge_options *opt,\n \t\t\t} else if (source_deleted) {\n \t\t\t\t/* rename/delete */\n \t\t\t\tnewinfo->path_conflict = 1;\n-\t\t\t\tpath_msg(opt, newpath, 0,\n+\t\t\t\tpath_msg(opt, CONFLICT_RENAME_DELETE, 0,\n+\t\t\t\t\t newpath, oldpath, NULL, NULL,\n \t\t\t\t\t _(\"CONFLICT (rename/delete): %s renamed\"\n \t\t\t\t\t   \" to %s in %s, but deleted in %s.\"),\n \t\t\t\t\t oldpath, newpath,\n@@ -3687,7 +3836,8 @@ static void process_entry(struct merge_options *opt,\n \t\tpath = unique_path(opt, path, branch);\n \t\tstrmap_put(&opt->priv->paths, path, new_ci);\n \n-\t\tpath_msg(opt, path, 0,\n+\t\tpath_msg(opt, CONFLICT_FILE_DIRECTORY, 0,\n+\t\t\t path, old_path, NULL, NULL,\n \t\t\t _(\"CONFLICT (file/directory): directory in the way \"\n \t\t\t   \"of %s from %s; moving it to %s instead.\"),\n \t\t\t old_path, branch, path);\n@@ -3763,15 +3913,23 @@ static void process_entry(struct merge_options *opt,\n \t\t\t\trename_b = 1;\n \t\t\t}\n \n+\t\t\tif (rename_a)\n+\t\t\t\ta_path = unique_path(opt, path, opt->branch1);\n+\t\t\tif (rename_b)\n+\t\t\t\tb_path = unique_path(opt, path, opt->branch2);\n+\n \t\t\tif (rename_a && rename_b) {\n-\t\t\t\tpath_msg(opt, path, 0,\n+\t\t\t\tpath_msg(opt, CONFLICT_DISTINCT_MODES, 0,\n+\t\t\t\t\t path, a_path, b_path, NULL,\n \t\t\t\t\t _(\"CONFLICT (distinct types): %s had \"\n \t\t\t\t\t   \"different types on each side; \"\n \t\t\t\t\t   \"renamed both of them so each can \"\n \t\t\t\t\t   \"be recorded somewhere.\"),\n \t\t\t\t\t path);\n \t\t\t} else {\n-\t\t\t\tpath_msg(opt, path, 0,\n+\t\t\t\tpath_msg(opt, CONFLICT_DISTINCT_MODES, 0,\n+\t\t\t\t\t path, rename_a ? a_path : b_path,\n+\t\t\t\t\t NULL, NULL,\n \t\t\t\t\t _(\"CONFLICT (distinct types): %s had \"\n \t\t\t\t\t   \"different types on each side; \"\n \t\t\t\t\t   \"renamed one of them so each can be \"\n@@ -3808,14 +3966,10 @@ static void process_entry(struct merge_options *opt,\n \n \t\t\t/* Insert entries into opt->priv_paths */\n \t\t\tassert(rename_a || rename_b);\n-\t\t\tif (rename_a) {\n-\t\t\t\ta_path = unique_path(opt, path, opt->branch1);\n+\t\t\tif (rename_a)\n \t\t\t\tstrmap_put(&opt->priv->paths, a_path, ci);\n-\t\t\t}\n \n-\t\t\tif (rename_b)\n-\t\t\t\tb_path = unique_path(opt, path, opt->branch2);\n-\t\t\telse\n+\t\t\tif (!rename_b)\n \t\t\t\tb_path = path;\n \t\t\tstrmap_put(&opt->priv->paths, b_path, new_ci);\n \n@@ -3866,7 +4020,8 @@ static void process_entry(struct merge_options *opt,\n \t\t\t\treason = _(\"add/add\");\n \t\t\tif (S_ISGITLINK(merged_file.mode))\n \t\t\t\treason = _(\"submodule\");\n-\t\t\tpath_msg(opt, path, 0,\n+\t\t\tpath_msg(opt, CONFLICT_CONTENTS, 0,\n+\t\t\t\t path, NULL, NULL, NULL,\n \t\t\t\t _(\"CONFLICT (%s): Merge conflict in %s\"),\n \t\t\t\t reason, path);\n \t\t}\n@@ -3910,7 +4065,8 @@ static void process_entry(struct merge_options *opt,\n \t\t\t * since the contents were not modified.\n \t\t\t */\n \t\t} else {\n-\t\t\tpath_msg(opt, path, 0,\n+\t\t\tpath_msg(opt, CONFLICT_MODIFY_DELETE, 0,\n+\t\t\t\t path, NULL, NULL, NULL,\n \t\t\t\t _(\"CONFLICT (modify/delete): %s deleted in %s \"\n \t\t\t\t   \"and modified in %s.  Version %s of %s left \"\n \t\t\t\t   \"in tree.\"),\n@@ -4206,7 +4362,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)\n \t\t\t\t\t\t\t\t     path,\n \t\t\t\t\t\t\t\t     \"cruft\");\n \n-\t\t\t\t\tpath_msg(opt, path, 1,\n+\t\t\t\t\tpath_msg(opt, CONFLICT_PRESENT_DESPITE_SKIPPED, 1,\n+\t\t\t\t\t\t path, NULL, NULL, NULL,\n \t\t\t\t\t\t _(\"Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s\"),\n \t\t\t\t\t\t path, new_name);\n \t\t\t\t\terrs |= rename(path, new_name);\n-- \ngitgitgadget\n\n"},{"id":"457484","messageId":"f523b08ab5af32d1074d016e5d122df688f5371d.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 11/17] merge-ort: store messages in a list, not in a single strbuf","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:54Z","receivedAt":"2022-06-18T00:21:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nTo prepare for using the `merge-ort` machinery in server operations, we\ncannot simply produce a free-form string that combines a variable-length\nlist of messages.\n\nInstead, we need to list them one by one. The natural fit for this is a\n`string_list`.\n\nWe will subsequently add even more information in the `util` attribute\nof the string list items.\n\nBased-on-a-patch-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-ort.c | 123 ++++++++++++++++++++++++++++++++++------------------\n merge-ort.h |   2 +-\n 2 files changed, 81 insertions(+), 44 deletions(-)\n\ndiff --git a/merge-ort.c b/merge-ort.c\nindex 7e8b9cd6ea7..668aec64f13 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -349,13 +349,15 @@ struct merge_options_internal {\n \tstruct mem_pool pool;\n \n \t/*\n-\t * output: special messages and conflict notices for various paths\n+\t * conflicts: logical conflicts and messages stored by _primary_ path\n \t *\n \t * This is a map of pathnames (a subset of the keys in \"paths\" above)\n-\t * to strbufs.  It gathers various warning/conflict/notice messages\n-\t * for later processing.\n+\t * to struct string_list, with each item's `util` containing a\n+\t * `struct logical_conflict_info`. Note, though, that for each path,\n+\t * it only stores the logical conflicts for which that path is the\n+\t * primary path; the path might be part of additional conflicts.\n \t */\n-\tstruct strmap output;\n+\tstruct strmap conflicts;\n \n \t/*\n \t * renames: various data relating to rename detection\n@@ -567,20 +569,20 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,\n \t\tstruct strmap_entry *e;\n \n \t\t/* Release and free each strbuf found in output */\n-\t\tstrmap_for_each_entry(&opti->output, &iter, e) {\n-\t\t\tstruct strbuf *sb = e->value;\n-\t\t\tstrbuf_release(sb);\n+\t\tstrmap_for_each_entry(&opti->conflicts, &iter, e) {\n+\t\t\tstruct string_list *list = e->value;\n \t\t\t/*\n-\t\t\t * While strictly speaking we don't need to free(sb)\n-\t\t\t * here because we could pass free_values=1 when\n-\t\t\t * calling strmap_clear() on opti->output, that would\n-\t\t\t * require strmap_clear to do another\n-\t\t\t * strmap_for_each_entry() loop, so we just free it\n-\t\t\t * while we're iterating anyway.\n+\t\t\t * While strictly speaking we don't need to\n+\t\t\t * free(conflicts) here because we could pass\n+\t\t\t * free_values=1 when calling strmap_clear() on\n+\t\t\t * opti->conflicts, that would require strmap_clear\n+\t\t\t * to do another strmap_for_each_entry() loop, so we\n+\t\t\t * just free it while we're iterating anyway.\n \t\t\t */\n-\t\t\tfree(sb);\n+\t\t\tstring_list_clear(list, 1);\n+\t\t\tfree(list);\n \t\t}\n-\t\tstrmap_clear(&opti->output, 0);\n+\t\tstrmap_clear(&opti->conflicts, 0);\n \t}\n \n \tmem_pool_discard(&opti->pool, 0);\n@@ -634,7 +636,9 @@ static void path_msg(struct merge_options *opt,\n \t\t     const char *fmt, ...)\n {\n \tva_list ap;\n-\tstruct strbuf *sb, *dest;\n+\tstruct string_list *path_conflicts;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tstruct strbuf *dest;\n \tstruct strbuf tmp = STRBUF_INIT;\n \n \tif (opt->record_conflict_msgs_as_headers && omittable_hint)\n@@ -642,14 +646,16 @@ static void path_msg(struct merge_options *opt,\n \tif (opt->priv->call_depth && opt->verbosity < 5)\n \t\treturn; /* Ignore messages from inner merges */\n \n-\tsb = strmap_get(&opt->priv->output, path);\n-\tif (!sb) {\n-\t\tsb = xmalloc(sizeof(*sb));\n-\t\tstrbuf_init(sb, 0);\n-\t\tstrmap_put(&opt->priv->output, path, sb);\n+\t/* Ensure path_conflicts (ptr to array of logical_conflict) allocated */\n+\tpath_conflicts = strmap_get(&opt->priv->conflicts, path);\n+\tif (!path_conflicts) {\n+\t\tpath_conflicts = xmalloc(sizeof(*path_conflicts));\n+\t\tstring_list_init_dup(path_conflicts);\n+\t\tstrmap_put(&opt->priv->conflicts, path, path_conflicts);\n \t}\n \n-\tdest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);\n+\t/* Handle message and its format, in normal case */\n+\tdest = (opt->record_conflict_msgs_as_headers ? &tmp : &buf);\n \n \tva_start(ap, fmt);\n \tif (opt->priv->call_depth) {\n@@ -660,32 +666,31 @@ static void path_msg(struct merge_options *opt,\n \tstrbuf_vaddf(dest, fmt, ap);\n \tva_end(ap);\n \n+\t/* Handle specialized formatting of message under --remerge-diff */\n \tif (opt->record_conflict_msgs_as_headers) {\n \t\tint i_sb = 0, i_tmp = 0;\n \n \t\t/* Start with the specified prefix */\n \t\tif (opt->msg_header_prefix)\n-\t\t\tstrbuf_addf(sb, \"%s \", opt->msg_header_prefix);\n+\t\t\tstrbuf_addf(&buf, \"%s \", opt->msg_header_prefix);\n \n \t\t/* Copy tmp to sb, adding spaces after newlines */\n-\t\tstrbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */\n+\t\tstrbuf_grow(&buf, buf.len + 2*tmp.len); /* more than sufficient */\n \t\tfor (; i_tmp < tmp.len; i_tmp++, i_sb++) {\n \t\t\t/* Copy next character from tmp to sb */\n-\t\t\tsb->buf[sb->len + i_sb] = tmp.buf[i_tmp];\n+\t\t\tbuf.buf[buf.len + i_sb] = tmp.buf[i_tmp];\n \n \t\t\t/* If we copied a newline, add a space */\n \t\t\tif (tmp.buf[i_tmp] == '\\n')\n-\t\t\t\tsb->buf[++i_sb] = ' ';\n+\t\t\t\tbuf.buf[++i_sb] = ' ';\n \t\t}\n \t\t/* Update length and ensure it's NUL-terminated */\n-\t\tsb->len += i_sb;\n-\t\tsb->buf[sb->len] = '\\0';\n+\t\tbuf.len += i_sb;\n+\t\tbuf.buf[buf.len] = '\\0';\n \n \t\tstrbuf_release(&tmp);\n \t}\n-\n-\t/* Add final newline character to sb */\n-\tstrbuf_addch(sb, '\\n');\n+\tstring_list_append_nodup(path_conflicts, strbuf_detach(&buf, NULL));\n }\n \n static struct diff_filespec *pool_alloc_filespec(struct mem_pool *pool,\n@@ -4256,7 +4261,6 @@ void merge_display_update_messages(struct merge_options *opt,\n \tstruct hashmap_iter iter;\n \tstruct strmap_entry *e;\n \tstruct string_list olist = STRING_LIST_INIT_NODUP;\n-\tint i;\n \n \tif (opt->record_conflict_msgs_as_headers)\n \t\tBUG(\"Either display conflict messages or record them as headers, not both\");\n@@ -4264,20 +4268,20 @@ void merge_display_update_messages(struct merge_options *opt,\n \ttrace2_region_enter(\"merge\", \"display messages\", opt->repo);\n \n \t/* Hack to pre-allocate olist to the desired size */\n-\tALLOC_GROW(olist.items, strmap_get_size(&opti->output),\n+\tALLOC_GROW(olist.items, strmap_get_size(&opti->conflicts),\n \t\t   olist.alloc);\n \n \t/* Put every entry from output into olist, then sort */\n-\tstrmap_for_each_entry(&opti->output, &iter, e) {\n+\tstrmap_for_each_entry(&opti->conflicts, &iter, e) {\n \t\tstring_list_append(&olist, e->key)->util = e->value;\n \t}\n \tstring_list_sort(&olist);\n \n \t/* Iterate over the items, printing them */\n-\tfor (i = 0; i < olist.nr; ++i) {\n-\t\tstruct strbuf *sb = olist.items[i].util;\n-\n-\t\tprintf(\"%s\", sb->buf);\n+\tfor (int path_nr = 0; path_nr < olist.nr; ++path_nr) {\n+\t\tstruct string_list *conflicts = olist.items[path_nr].util;\n+\t\tfor (int i = 0; i < conflicts->nr; i++)\n+\t\t\tputs(conflicts->items[i].string);\n \t}\n \tstring_list_clear(&olist, 0);\n \n@@ -4366,6 +4370,8 @@ void merge_finalize(struct merge_options *opt,\n \t\t    struct merge_result *result)\n {\n \tstruct merge_options_internal *opti = result->priv;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n \n \tif (opt->renormalize)\n \t\tgit_attr_set_direction(GIT_ATTR_CHECKIN);\n@@ -4373,6 +4379,15 @@ void merge_finalize(struct merge_options *opt,\n \n \tclear_or_reinit_internal_opts(opti, 0);\n \tFREE_AND_NULL(opti);\n+\n+\t/* Release and free each strbuf found in path_messages */\n+\tstrmap_for_each_entry(result->path_messages, &iter, e) {\n+\t\tstruct strbuf *buf = e->value;\n+\n+\t\tstrbuf_release(buf);\n+\t}\n+\tstrmap_clear(result->path_messages, 1);\n+\tFREE_AND_NULL(result->path_messages);\n }\n \n /*** Function Grouping: helper functions for merge_incore_*() ***/\n@@ -4531,11 +4546,11 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)\n \tstrmap_init_with_options(&opt->priv->conflicted, pool, 0);\n \n \t/*\n-\t * keys & strbufs in output will sometimes need to outlive \"paths\",\n-\t * so it will have a copy of relevant keys.  It's probably a small\n-\t * subset of the overall paths that have special output.\n+\t * keys & string_lists in conflicts will sometimes need to outlive\n+\t * \"paths\", so it will have a copy of relevant keys.  It's probably\n+\t * a small subset of the overall paths that have special output.\n \t */\n-\tstrmap_init(&opt->priv->output);\n+\tstrmap_init(&opt->priv->conflicts);\n \n \ttrace2_region_leave(\"merge\", \"allocate/init\", opt->repo);\n }\n@@ -4596,6 +4611,8 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,\n \t\t\t\t\t    struct merge_result *result)\n {\n \tstruct object_id working_tree_oid;\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n \n \tif (opt->subtree_shift) {\n \t\tside2 = shift_tree_object(opt->repo, side1, side2,\n@@ -4636,7 +4653,27 @@ redo:\n \ttrace2_region_leave(\"merge\", \"process_entries\", opt->repo);\n \n \t/* Set return values */\n-\tresult->path_messages = &opt->priv->output;\n+\tresult->path_messages = xcalloc(1, sizeof(*result->path_messages));\n+\tstrmap_init_with_options(result->path_messages, NULL, 0);\n+\tstrmap_for_each_entry(&opt->priv->conflicts, &iter, e) {\n+\t\tconst char *path = e->key;\n+\t\tstruct strbuf *buf = strmap_get(result->path_messages, path);\n+\t\tstruct string_list *conflicts = e->value;\n+\n+\t\tif (!buf) {\n+\t\t\tbuf = xcalloc(1, sizeof(*buf));\n+\t\t\tstrbuf_init(buf, 0);\n+\t\t\tstrmap_put(result->path_messages, path, buf);\n+\t\t}\n+\n+\t\tfor (int i = 0; i < conflicts->nr; i++) {\n+\t\t\tif (buf->len)\n+\t\t\t\tstrbuf_addch(buf, '\\n');\n+\t\t\tstrbuf_addstr(buf, conflicts->items[i].string);\n+\t\t\tstrbuf_trim_trailing_newline(buf);\n+\t\t}\n+\t}\n+\n \tresult->tree = parse_tree_indirect(&working_tree_oid);\n \t/* existence of conflicted entries implies unclean */\n \tresult->clean &= strmap_empty(&opt->priv->conflicted);\ndiff --git a/merge-ort.h b/merge-ort.h\nindex ddcc39d7270..f9c536ed8c4 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -28,7 +28,7 @@ struct merge_result {\n \t/*\n \t * Special messages and conflict notices for various paths\n \t *\n-\t * This is a map of pathnames to strbufs.  It contains various\n+\t * This is a map of pathnames to strbufs. It contains various\n \t * warning/conflict/notice messages (possibly multiple per path)\n \t * that callers may want to use.\n \t */\n-- \ngitgitgadget\n\n"},{"id":"457485","messageId":"d7360f58f166f46bb3fc977e3cd71cdbe23a8473.1655511660.git.gitgitgadget@gmail.com","threadId":"57288","inReplyTo":"pull.1122.v7.git.1655511660.gitgitgadget@gmail.com","subject":"[PATCH v7 10/17] merge-tree: provide easy access to `ls-files -u` style info","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-06-18T00:20:53Z","receivedAt":"2022-06-18T00:21:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nMuch like `git merge` updates the index with information of the form\n    (mode, oid, stage, name)\nprovide this output for conflicted files for merge-tree as well.\nProvide a --name-only option for users to exclude the mode, oid, and\nstage and only get the list of conflicted filenames.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge-tree.txt | 34 ++++++++++++++++++++++++++------\n builtin/merge-tree.c             | 11 ++++++++++-\n t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++--\n 3 files changed, 62 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt\nindex 68a51c82618..b89aabdb98e 100644\n--- a/Documentation/git-merge-tree.txt\n+++ b/Documentation/git-merge-tree.txt\n@@ -40,6 +40,13 @@ After the merge completes, a new toplevel tree object is created.  See\n OPTIONS\n -------\n \n+--name-only::\n+\tIn the Conflicted file info section, instead of writing a list\n+\tof (mode, oid, stage, path) tuples to output for conflicted\n+\tfiles, just provide a list of filenames with conflicts (and\n+\tdo not list filenames multiple times if they have multiple\n+\tconflicting stages).\n+\n --[no-]messages::\n \tWrite any informational messages such as \"Auto-merging <path>\"\n \tor CONFLICT notices to the end of stdout.  If unspecified, the\n@@ -58,7 +65,7 @@ line:\n Whereas for a conflicted merge, the output is by default of the form:\n \n \t<OID of toplevel tree>\n-\t<Conflicted file list>\n+\t<Conflicted file info>\n \t<Informational messages>\n \n These are discussed individually below.\n@@ -72,19 +79,24 @@ working tree at the end of `git merge`.  If there were conflicts, then\n files within this tree may have embedded conflict markers.\n \n [[CFI]]\n-Conflicted file list\n+Conflicted file info\n ~~~~~~~~~~~~~~~~~~~~\n \n-This is a sequence of lines containing a filename on each line, quoted\n-as explained for the configuration variable `core.quotePath` (see\n-linkgit:git-config[1]).\n+This is a sequence of lines with the format\n+\n+\t<mode> <object> <stage> <filename>\n+\n+The filename will be quoted as explained for the configuration\n+variable `core.quotePath` (see linkgit:git-config[1]).  However, if\n+the `--name-only` option is passed, the mode, object, and stage will\n+be omitted.\n \n [[IM]]\n Informational messages\n ~~~~~~~~~~~~~~~~~~~~~~\n \n This always starts with a blank line to separate it from the previous\n-section, and then has free-form messages about the merge, such as:\n+sections, and then has free-form messages about the merge, such as:\n \n   * \"Auto-merging <file>\"\n   * \"CONFLICT (rename/delete): <oldfile> renamed...but deleted in...\"\n@@ -116,6 +128,16 @@ used as a part of a series of steps such as:\n Note that when the exit status is non-zero, `NEWTREE` in this sequence\n will contain a lot more output than just a tree.\n \n+For conflicts, the output includes the same information that you'd get\n+with linkgit:git-merge[1]:\n+\n+  * what would be written to the working tree (the\n+    <<OIDTLT,OID of toplevel tree>>)\n+  * the higher order stages that would be written to the index (the\n+    <<CFI,Conflicted file info>>)\n+  * any messages that would have been printed to stdout (the\n+    <<IM,Informational messages>>)\n+\n [[DEPMERGE]]\n DEPRECATED DESCRIPTION\n ----------------------\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 13a9536f7c1..c61b5b4a10d 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -400,6 +400,7 @@ enum mode {\n struct merge_tree_options {\n \tint mode;\n \tint show_messages;\n+\tint name_only;\n };\n \n static int real_merge(struct merge_tree_options *o,\n@@ -453,7 +454,11 @@ static int real_merge(struct merge_tree_options *o,\n \t\tmerge_get_conflicted_files(&result, &conflicted_files);\n \t\tfor (i = 0; i < conflicted_files.nr; i++) {\n \t\t\tconst char *name = conflicted_files.items[i].string;\n-\t\t\tif (last && !strcmp(last, name))\n+\t\t\tstruct stage_info *c = conflicted_files.items[i].util;\n+\t\t\tif (!o->name_only)\n+\t\t\t\tprintf(\"%06o %s %d\\t\",\n+\t\t\t\t       c->mode, oid_to_hex(&c->oid), c->stage);\n+\t\t\telse if (last && !strcmp(last, name))\n \t\t\t\tcontinue;\n \t\t\twrite_name_quoted_relative(\n \t\t\t\tname, prefix, stdout, line_termination);\n@@ -488,6 +493,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n \t\t\t    N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n \t\tOPT_BOOL(0, \"messages\", &o.show_messages,\n \t\t\t N_(\"also show informational/conflict messages\")),\n+\t\tOPT_BOOL_F(0, \"name-only\",\n+\t\t\t   &o.name_only,\n+\t\t\t   N_(\"list filenames without modes/oids/stages\"),\n+\t\t\t   PARSE_OPT_NONEG),\n \t\tOPT_END()\n \t};\n \ndiff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh\nindex 8e6dba44288..0ec5f0d3f7e 100755\n--- a/t/t4301-merge-tree-write-tree.sh\n+++ b/t/t4301-merge-tree-write-tree.sh\n@@ -65,6 +65,7 @@ test_expect_success 'Content merge and a few conflicts' '\n \texpected_tree=$(git rev-parse AUTO_MERGE) &&\n \n \t# We will redo the merge, while we are still in a conflicted state!\n+\tgit ls-files -u >conflicted-file-info &&\n \ttest_when_finished \"git reset --hard\" &&\n \n \ttest_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&\n@@ -108,7 +109,7 @@ anonymize_hash() {\n }\n \n test_expect_success 'test conflict notices and such' '\n-\ttest_expect_code 1 git merge-tree --write-tree side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \t# Expected results:\n@@ -143,7 +144,7 @@ do\n done\n \n test_expect_success 'Just the conflicted files without the messages' '\n-\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out &&\n \tanonymize_hash out >actual &&\n \n \ttest_write_lines HASH greeting whatever~side1 >expect &&\n@@ -151,4 +152,25 @@ test_expect_success 'Just the conflicted files without the messages' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'Check conflicted oids and modes without messages' '\n+\ttest_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&\n+\tanonymize_hash out >actual &&\n+\n+\t# Compare the basic output format\n+\tq_to_tab >expect <<-\\EOF &&\n+\tHASH\n+\t100644 HASH 1Qgreeting\n+\t100644 HASH 2Qgreeting\n+\t100644 HASH 3Qgreeting\n+\t100644 HASH 1Qwhatever~side1\n+\t100644 HASH 2Qwhatever~side1\n+\tEOF\n+\n+\ttest_cmp expect actual &&\n+\n+\t# Check the actual hashes against the `ls-files -u` output too\n+\ttail -n +2 out | sed -e s/side1/HEAD/ >actual &&\n+\ttest_cmp conflicted-file-info actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"457505","messageId":"nycvar.QRO.7.76.6.2206182357180.349@tvgsbejvaqbjf.bet","threadId":"57288","inReplyTo":"CABPp-BFfRGQybYManC6LfFTaAd3mweBxL=APCGP_vRF-WQxGSw@mail.gmail.com","subject":"Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-06-18T21:58:02Z","receivedAt":"2022-06-18T21:58:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Elijah,\n\nOn Fri, 17 Jun 2022, Elijah Newren wrote:\n\n> On Tue, Jun 7, 2022 at 12:38 AM Elijah Newren <newren@gmail.com> wrote:\n> >\n> > On Mon, Jun 6, 2022 at 2:37 PM Johannes Schindelin\n> > <Johannes.Schindelin@gmx.de> wrote:\n> > >\n> > > [after this update, I will shut up until you chime in, promise!]\n> > >\n> > > On Mon, 6 Jun 2022, Johannes Schindelin wrote:\n> > >\n> > > > On Sun, 5 Jun 2022, Johannes Schindelin wrote:\n> > > >\n> > > > > On Sat, 4 Jun 2022, Johannes Schindelin wrote:\n> > > > >\n> > > > > > It would be great if you could have a quick look over the commits I\n> > > > > > added on top of your branch, to see whether things make more or less\n> > > > > > sense to you. But if you're too busy elsewhere, I am one of the best\n> > > > > > persons to understand that, too.\n> > > > >\n> > > > > Hopefully I will get this into a reviewable shape before you get to\n> > > > > looking at it, so that your time is spent more wisely than what I\n> > > > > asked for ;-)\n> > > >\n> > > > I hope to find some time to work on this more tomorrow; If not, I will\n> > > > get back to the project on Wednesday and push it further.\n> > >\n> > > I did get a chance, and polished the patch series a bit. I also rebased it\n> > > onto the current tip of Git's main branch, mainly to address some merge\n> > > conflicts preemptively. The result is pushed up to\n> > > https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the\n> > > range-diff relative to `newren/wip-for-in-core-merge-tree`:\n> >\n> > This is so awesome; thanks for working on this.  Sorry I haven't had\n> > time to review yet, but I'm hoping to be able to near the end of this\n> > week.  I'm excited to see how it looks.  :-)\n> >\n> > > -- snip --\n> > >  1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases\n> > >  2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb\n> > >  3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability\n> > >  4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`\n> > >  5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings\n> > >  6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion\n> > >  7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable\n> > >  8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers\n> > >  9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths\n> > > 10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers\n> > > 11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable\n> > > 12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges\n> > > 13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()\n> > > 14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function\n> > > 15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function\n> > > 16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges\n> > > 17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function\n> > > 18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output\n> > > 19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function\n> > > 25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort\n> > >     @@ merge-ort.c: static int merge_submodule(struct merge_options *opt,\n> > >                 strbuf_release(&sb);\n> > >                 break;\n> > >         default:\n> > >     +\n> > >     + ## t/t6437-submodule-merge.sh ##\n> > >     +@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '\n> > >     +   (cd merge-search &&\n> > >     +    git checkout -b test-nonforward b &&\n> > >     +    (cd sub &&\n> > >     +-    git rev-parse sub-d > ../expect) &&\n> > >     ++    git rev-parse --short sub-d > ../expect) &&\n> > >     +     if test \"$GIT_TEST_MERGE_ALGORITHM\" = ort\n> > >     +     then\n> > >     +           test_must_fail git merge c >actual\n> > > 20:  7b1ee417f3d !  9:  c92b81e7366 merge-tree: provide a list of which files have conflicts\n> > >     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n> > >      +          string_list_clear(&conflicted_files, 1);\n> > >      +  }\n> > >         if (o->show_messages) {\n> > >     -           printf(\"\\n\");\n> > >     +-          printf(\"\\n\");\n> > >     ++          putchar(line_termination);\n> > >                 merge_display_update_messages(&opt, &result);\n> > >     +   }\n> > >     +   merge_finalize(&opt, &result);\n> > >      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> > >\n> > >         /* Do the relevant type of merge */\n> > > 21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info\n> > >  -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf\n> > >  -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list\n> > >  -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information\n> > >  -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output\n> > > 22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated\n> > >     @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n> > >         if (!result.clean) {\n> > >                 struct string_list conflicted_files = STRING_LIST_INIT_NODUP;\n> > >                 const char *last = NULL;\n> > >     -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,\n> > >     -           string_list_clear(&conflicted_files, 1);\n> > >     -   }\n> > >     -   if (o->show_messages) {\n> > >     --          printf(\"\\n\");\n> > >     -+          putchar(line_termination);\n> > >     -           merge_display_update_messages(&opt, &result);\n> > >     -   }\n> > >     -   merge_finalize(&opt, &result);\n> > >      @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)\n> > >                             N_(\"do a trivial merge only\"), MODE_TRIVIAL),\n> > >                 OPT_BOOL(0, \"messages\", &o.show_messages,\n> > >     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n> > >      +\n> > >      +  test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&\n> > >      +  anonymize_hash out >actual &&\n> > >     ++  printf \"\\\\n\" >>actual &&\n> > >      +\n> > >      +  # Expected results:\n> > >      +  #   \"greeting\" should merge with conflicts\n> > >     @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and\n> > >      +\n> > >      +  EOF\n> > >      +\n> > >     -+  cat <<-EOF >>expect &&\n> > >     -+  Auto-merging greeting\n> > >     -+  CONFLICT (content): Merge conflict in greeting\n> > >     -+  CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n> > >     -+  CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n> > >     -+  Auto-merging Αυτά μου φαίνονται κινέζικα\n> > >     -+  CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n> > >     ++  q_to_nul <<-EOF >>expect &&\n> > >     ++  1QgreetingQAuto-mergingQAuto-merging greeting\n> > >     ++  Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting\n> > >     ++  Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.\n> > >     ++  Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.\n> > >     ++  Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα\n> > >     ++  Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα\n> > >     ++  Q\n> > >      +  EOF\n> > >      +\n> > >      +  test_cmp expect actual\n> > > 23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag\n> > > 24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes\n> > >     @@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:\n> > >      +<<IM,Informational messages>> section has the necessary info, though it\n> > >      +is not designed to be machine parseable.\n> > >      +\n> > >     ++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and\n> > >     ++the logical conflicts in the <<IM,Informational messages>> have a\n> > >     ++one-to-one mapping, nor that there is a one-to-many mapping, nor a\n> > >     ++many-to-one mapping.  Many-to-many mappings exist, meaning that each\n> > >     ++path can have many logical conflict types in a single merge, and each\n> > >     ++logical conflict type can affect many paths.\n> > >     ++\n> > >      +Do NOT assume all filenames listed in the <<IM,Informational messages>>\n> > >      +section had conflicts.  Messages can be included for files that have no\n> > >      +conflicts, such as \"Auto-merging <file>\".\n> > > 26:  78e1243eca1 <  -:  ----------- WIP\n> > > -- snap --\n> > >\n> > > I am pretty happy with the current state of the patches, and hope that we\n> > > can push this patch series over the finish line.\n> > >\n> > > If you can think of anything I can do to help with this, please do let me\n> > > know, I am _very_ interested in getting this done, and finally am in a\n> > > position to help.\n> >\n> > Very much appreciated.  Looks like you're now blocking on my review,\n> > so I'll try to make some time by end of week to look over things.\n>\n> Sorry for the delay.  However, I have looked over the changes in\n> detail.  Well done!  I very much like this version.  Thanks for taking\n> the time to break up my WIP patches, fix my bugs, address all the\n> FIXMEs, and then going and adding some nice cleanups and improvements\n> on top!  I'll add my Signed-off-by on a couple patches and have\n> gitgitgadget submit this round.\n\nThat's great news, thank you so much!\n\nCiao,\nDscho\n"}]}