{"thread":{"id":"53755","subject":"[RFC PATCH v1 00/17] Rewrite the remaining merge strategies from shell to C","startedAt":"2020-06-25T12:48:57Z","lastAt":"2022-12-15T08:53:26Z","messageCount":221,"participants":["Alban Gruin","Chris Torek","Phillip Wood","Junio C Hamano","Johannes Schindelin","SZEDER Gábor","Derrick Stolee","Martin Ågren","Ævar Arnfjörð Bjarmason","Elijah Newren","Taylor Blau"],"isPatch":true,"patchVersion":1,"patchTotal":17},"messages":[{"id":"400584","messageId":"20200625121953.16991-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":null,"subject":"[RFC PATCH v1 00/17] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:36Z","receivedAt":"2020-06-25T12:48:57Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In an effort to reduce the number of shell scripts part of git, I\npropose this patch converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on this series.\n\nPatches 2-5 rewrite, clean, and libify `git merge-one-file', used by the\nresolve and octopus strategies.\n\nPatch 6 libifies `git merge-index', so the rewritten `git\nmerge-one-file' can be called without forking.\n\nPatch 7-8-9 rewrite, clean, and libify `git merge-resolve'.\n\nPatch 10 moves a function, better_branch_name(), that will prove itself\nuseful in the C version of `git merge-octopus', but that is not part of\nlibgit.a.\n\nPatches 11-12-13 rewrite, clean, and libify `git merge-octopus'.\n\nPatches 14-15-16-17 teach `git merge' and the sequencer to call the\nstrategies without forking.\n\nThis series keeps the commands `git merge-one-file', `git merge-resolve'\nand `git merge-octopus', so any script depending on them should keep\nworking without any changes.\n\nThis series is based on c9c318d6bf (The fourth batch, 2020-06-22).  The\ntip is tagged as \"rewrite-and-cleanup-merge-strategies-v1\" at\nhttps://github.com/agrn/git.\n\nAlban Gruin (17):\n  t6027: modernise tests\n  merge-one-file: rewrite in C\n  merge-one-file: remove calls to external processes\n  merge-one-file: use error() instead of fprintf(stderr, ...)\n  merge-one-file: libify merge_one_file()\n  merge-index: libify merge_one_path() and merge_all()\n  merge-resolve: rewrite in C\n  merge-resolve: remove calls to external processes\n  merge-resolve: libify merge_resolve()\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge-octopus: remove calls to external processes\n  merge-octopus: libify merge_octopus()\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Makefile                        |   7 +-\n builtin.h                       |   3 +\n builtin/merge-index.c           |  77 +----\n builtin/merge-octopus.c         |  65 ++++\n builtin/merge-one-file.c        |  74 ++++\n builtin/merge-recursive.c       |  16 +-\n builtin/merge-resolve.c         |  69 ++++\n builtin/merge.c                 |   9 +-\n cache.h                         |   2 +-\n git-merge-octopus.sh            | 112 -------\n git-merge-one-file.sh           | 167 ---------\n git-merge-resolve.sh            |  54 ---\n git.c                           |   3 +\n merge-strategies.c              | 577 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  44 +++\n merge.c                         |  12 +\n sequencer.c                     |  16 +-\n t/t6027-merge-binary.sh         |  27 +-\n t/t6035-merge-dir-to-symlink.sh |   2 +-\n 19 files changed, 889 insertions(+), 447 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400585","messageId":"20200625121953.16991-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 01/17] t6027: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:37Z","receivedAt":"2020-06-25T12:49:00Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6027 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6027-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh\nindex 4e6c7cb77e..071d3f7343 100755\n--- a/t/t6027-merge-binary.sh\n+++ b/t/t6027-merge-binary.sh\n@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -35,33 +34,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400586","messageId":"20200625121953.16991-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 03/17] merge-one-file: remove calls to external processes","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:39Z","receivedAt":"2020-06-25T12:49:03Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To save precious cycles by avoiding reading and flushing the index\nrepeatedly, or write temporary files when an operation can be performed\nin-memory, this removes call to external processes:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_cache_entry();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_cache();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and xdl_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are replaced by calls to\n   cache_file_exists();\n\n - calls to `update-index' are replaced by calls to add_file_to_cache().\n\nTo enable these changes, the index is read and written back in\ncmd_merge_one_file().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-one-file.c | 160 +++++++++++++++++++--------------------\n 1 file changed, 78 insertions(+), 82 deletions(-)\n\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nindex 4992a6cd30..d9ebd820cb 100644\n--- a/builtin/merge-one-file.c\n+++ b/builtin/merge-one-file.c\n@@ -27,54 +27,48 @@\n #include \"dir.h\"\n #include \"lockfile.h\"\n #include \"object-store.h\"\n-#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n-static int create_temp_file(const struct object_id *oid, struct strbuf *path)\n-{\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint ret;\n-\n-\tcp.git_cmd = 1;\n-\targv_array_pushl(&cp.args, \"unpack-file\", oid_to_hex(oid), NULL);\n-\tret = pipe_command(&cp, NULL, 0, path, 0, &err, 0);\n-\tif (!ret && path->len > 0)\n-\t\tstrbuf_trim_trailing_newline(path);\n-\n-\tfprintf(stderr, \"%.*s\", (int) err.len, err.buf);\n-\tstrbuf_release(&err);\n-\n-\treturn ret;\n-}\n-\n static int add_to_index_cacheinfo(unsigned int mode,\n \t\t\t\t  const struct object_id *oid, const char *path)\n {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct cache_entry *ce;\n+\tint len, option;\n \n-\tcp.git_cmd = 1;\n-\targv_array_pushl(&cp.args, \"update-index\", \"--add\", \"--cacheinfo\", NULL);\n-\targv_array_pushf(&cp.args, \"%o,%s,%s\", mode, oid_to_hex(oid), path);\n-\treturn run_command(&cp);\n-}\n+\tif (!verify_path(path, mode))\n+\t\treturn error(\"Invalid path '%s'\", path);\n \n-static int remove_from_index(const char *path)\n-{\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(&the_index, len);\n \n-\tcp.git_cmd = 1;\n-\targv_array_pushl(&cp.args, \"update-index\", \"--remove\", \"--\", path, NULL);\n-\treturn run_command(&cp);\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(0);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n+\tif (add_cache_entry(ce, option))\n+\t\treturn error(\"%s: cannot add to the index\", path);\n+\n+\treturn 0;\n }\n \n static int checkout_from_index(const char *path)\n {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct checkout state;\n+\tstruct cache_entry *ce;\n \n-\tcp.git_cmd = 1;\n-\targv_array_pushl(&cp.args, \"checkout-index\", \"-u\", \"-f\", \"--\", path, NULL);\n-\treturn run_command(&cp);\n+\tstate.istate = &the_index;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tce = cache_file_exists(path, strlen(path), 0);\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(\"%s: cannot checkout file\", path);\n+\treturn 0;\n }\n \n static int merge_one_file_deleted(const struct object_id *orig_blob,\n@@ -96,7 +90,9 @@ static int merge_one_file_deleted(const struct object_id *orig_blob,\n \t\t\tremove_path(path);\n \t}\n \n-\treturn remove_from_index(path);\n+\tif (remove_file_from_cache(path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n }\n \n static int do_merge_one_file(const struct object_id *orig_blob,\n@@ -104,61 +100,50 @@ static int do_merge_one_file(const struct object_id *orig_blob,\n \t\t\t     const struct object_id *their_blob, const char *path,\n \t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n {\n-\tint ret, source, dest;\n-\tstruct strbuf src1 = STRBUF_INIT, src2 = STRBUF_INIT, orig = STRBUF_INIT;\n-\tstruct child_process cp_merge = CHILD_PROCESS_INIT,\n-\t\tcp_checkout = CHILD_PROCESS_INIT,\n-\t\tcp_update = CHILD_PROCESS_INIT;\n+\tint ret, i, dest;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\tstruct cache_entry *ce;\n \n-\tif (our_mode == S_IFLNK || their_mode == S_IFLNK) {\n-\t\tfprintf(stderr, \"ERROR: %s: Not merging symbolic link changes.\\n\", path);\n-\t\treturn 1;\n-\t} else if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK) {\n-\t\tfprintf(stderr, \"ERROR: %s: Not merging conflicting submodule changes.\\n\",\n-\t\t\tpath);\n-\t\treturn 1;\n-\t}\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n \n-\tcreate_temp_file(our_blob, &src1);\n-\tcreate_temp_file(their_blob, &src2);\n+\tread_mmblob(mmfs + 0, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n \n \tif (orig_blob) {\n \t\tprintf(\"Auto-merging %s\\n\", path);\n-\t\tcreate_temp_file(orig_blob, &orig);\n+\t\tread_mmblob(mmfs + 1, orig_blob);\n \t} else {\n \t\tprintf(\"Added %s in both, but differently.\\n\", path);\n-\t\tcreate_temp_file(the_hash_algo->empty_blob, &orig);\n+\t\tread_mmblob(mmfs + 1, the_hash_algo->empty_blob);\n \t}\n \n-\tcp_merge.git_cmd = 1;\n-\targv_array_pushl(&cp_merge.args, \"merge-file\", src1.buf, orig.buf, src2.buf,\n-\t\t\t NULL);\n-\tret = run_command(&cp_merge);\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n \n-\tif (ret != 0)\n+\tret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret > 127)\n \t\tret = 1;\n \n-\tcp_checkout.git_cmd = 1;\n-\targv_array_pushl(&cp_checkout.args, \"checkout-index\", \"-f\", \"--stage=2\",\n-\t\t\t \"--\", path, NULL);\n-\tif (run_command(&cp_checkout))\n-\t\treturn 1;\n+\tce = cache_file_exists(path, strlen(path), 0);\n+\tif (!ce)\n+\t\tBUG(\"file is not present in the cache?\");\n \n-\tsource = open(src1.buf, O_RDONLY);\n-\tdest = open(path, O_WRONLY | O_TRUNC);\n-\n-\tcopy_fd(source, dest);\n-\n-\tclose(source);\n+\tunlink(path);\n+\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n+\twrite_in_full(dest, result.ptr, result.size);\n \tclose(dest);\n \n-\tunlink(orig.buf);\n-\tunlink(src1.buf);\n-\tunlink(src2.buf);\n-\n-\tstrbuf_release(&src1);\n-\tstrbuf_release(&src2);\n-\tstrbuf_release(&orig);\n+\tfree(result.ptr);\n \n \tif (ret) {\n \t\tfprintf(stderr, \"ERROR: \");\n@@ -178,9 +163,7 @@ static int do_merge_one_file(const struct object_id *orig_blob,\n \t\treturn 1;\n \t}\n \n-\tcp_update.git_cmd = 1;\n-\targv_array_pushl(&cp_update.args, \"update-index\", \"--\", path, NULL);\n-\treturn run_command(&cp_update);\n+\treturn add_file_to_cache(path, 0);\n }\n \n static int merge_one_file(const struct object_id *orig_blob,\n@@ -250,11 +233,17 @@ int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n {\n \tstruct object_id orig_blob, our_blob, their_blob,\n \t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n-\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \tif (argc != 8)\n \t\tusage(builtin_merge_one_file_usage);\n \n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\n \tif (!get_oid(argv[1], &orig_blob)) {\n \t\tp_orig_blob = &orig_blob;\n \t\torig_mode = strtol(argv[5], NULL, 8);\n@@ -270,6 +259,13 @@ int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n \t\ttheir_mode = strtol(argv[7], NULL, 8);\n \t}\n \n-\treturn merge_one_file(p_orig_blob, p_our_blob, p_their_blob, argv[4],\n-\t\t\t      orig_mode, our_mode, their_mode);\n+\tret = merge_one_file(p_orig_blob, p_our_blob, p_their_blob, argv[4],\n+\t\t\t     orig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn ret;\n+\t}\n+\n+\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n }\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400587","messageId":"20200625121953.16991-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 04/17] merge-one-file: use error() instead of fprintf(stderr, ...)","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:40Z","receivedAt":"2020-06-25T12:49:03Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"We have a handy helper function to display errors and return a value.\nUse it instead of fprintf(stderr, ...).\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-one-file.c | 43 +++++++++++++---------------------------\n 1 file changed, 14 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nindex d9ebd820cb..d612885723 100644\n--- a/builtin/merge-one-file.c\n+++ b/builtin/merge-one-file.c\n@@ -77,11 +77,9 @@ static int merge_one_file_deleted(const struct object_id *orig_blob,\n \t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n {\n \tif ((our_blob && orig_mode != our_mode) ||\n-\t    (their_blob && orig_mode != their_mode)) {\n-\t\tfprintf(stderr, \"ERROR: File %s deleted on one branch but had its\\n\", path);\n-\t\tfprintf(stderr, \"ERROR: permissions changed on the other.\\n\");\n-\t\treturn 1;\n-\t}\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n \n \tif (our_blob) {\n \t\tprintf(\"Removing %s\\n\", path);\n@@ -146,19 +144,11 @@ static int do_merge_one_file(const struct object_id *orig_blob,\n \tfree(result.ptr);\n \n \tif (ret) {\n-\t\tfprintf(stderr, \"ERROR: \");\n-\n-\t\tif (!orig_blob) {\n-\t\t\tfprintf(stderr, \"content conflict\");\n-\t\t\tif (our_mode != their_mode)\n-\t\t\t\tfprintf(stderr, \", \");\n-\t\t}\n-\n+\t\tif (!orig_blob)\n+\t\t\terror(_(\"content conflict in %s\"), path);\n \t\tif (our_mode != their_mode)\n-\t\t\tfprintf(stderr, \"permissions conflict: %o->%o,%o\",\n-\t\t\t\torig_mode, our_mode, their_mode);\n-\n-\t\tfprintf(stderr, \" in %s\\n\", path);\n+\t\t\terror(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t      orig_mode, our_mode, their_mode, path);\n \n \t\treturn 1;\n \t}\n@@ -181,22 +171,18 @@ static int merge_one_file(const struct object_id *orig_blob,\n \t} else if (!orig_blob && !our_blob && their_blob) {\n \t\tprintf(\"Adding %s\\n\", path);\n \n-\t\tif (file_exists(path)) {\n-\t\t\tfprintf(stderr, \"ERROR: untracked %s is overwritten by the merge.\\n\", path);\n-\t\t\treturn 1;\n-\t\t}\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n \n \t\tif (add_to_index_cacheinfo(their_mode, their_blob, path))\n \t\t\treturn 1;\n \t\treturn checkout_from_index(path);\n \t} else if (!orig_blob && our_blob && their_blob &&\n \t\t   oideq(our_blob, their_blob)) {\n-\t\tif (our_mode != their_mode) {\n-\t\t\tfprintf(stderr, \"ERROR: File %s added identically in both branches,\", path);\n-\t\t\tfprintf(stderr, \"ERROR: but permissions conflict %o->%o.\\n\",\n-\t\t\t\tour_mode, their_mode);\n-\t\t\treturn 1;\n-\t\t}\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n \n \t\tprintf(\"Adding %s\\n\", path);\n \n@@ -216,9 +202,8 @@ static int merge_one_file(const struct object_id *orig_blob,\n \t\tif (their_blob)\n \t\t\ttheir_hex = oid_to_hex(their_blob);\n \n-\t\tfprintf(stderr, \"ERROR: %s: Not handling case %s -> %s -> %s\\n\",\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n \t\t\tpath, orig_hex, our_hex, their_hex);\n-\t\treturn 1;\n \t}\n \n \treturn 0;\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400588","messageId":"20200625121953.16991-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 02/17] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:38Z","receivedAt":"2020-06-25T12:49:05Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is very\nstraightforward: it keeps using external processes to edit the index,\nfor instance.  Errors are also displayed with fprintf() instead of\nerror().  Both of these will be addressed in the next few commits,\nleading to its libification so its main function can be used from other\ncommands directly.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   2 +-\n builtin.h                       |   1 +\n builtin/merge-one-file.c        | 275 ++++++++++++++++++++++++++++++++\n git-merge-one-file.sh           | 167 -------------------\n git.c                           |   1 +\n t/t6035-merge-dir-to-symlink.sh |   2 +-\n 6 files changed, 279 insertions(+), 169 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n\ndiff --git a/Makefile b/Makefile\nindex 372139f1f2..19574f5133 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -596,7 +596,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -1089,6 +1088,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..9205d5ecdc 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -172,6 +172,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..4992a6cd30\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,275 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge script, called with\n+ *\n+ *   $1 - original file SHA1 (or empty)\n+ *   $2 - file in branch1 SHA1 (or empty)\n+ *   $3 - file in branch2 SHA1 (or empty)\n+ *   $4 - pathname in repository\n+ *   $5 - original file mode (or empty)\n+ *   $6 - file in branch1 mode (or empty)\n+ *   $7 - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases.. The _really_ trivial cases have\n+ * been handled already by git read-tree, but that one doesn't\n+ * do any merges that might change the tree layout.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"dir.h\"\n+#include \"lockfile.h\"\n+#include \"object-store.h\"\n+#include \"run-command.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int create_temp_file(const struct object_id *oid, struct strbuf *path)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_pushl(&cp.args, \"unpack-file\", oid_to_hex(oid), NULL);\n+\tret = pipe_command(&cp, NULL, 0, path, 0, &err, 0);\n+\tif (!ret && path->len > 0)\n+\t\tstrbuf_trim_trailing_newline(path);\n+\n+\tfprintf(stderr, \"%.*s\", (int) err.len, err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn ret;\n+}\n+\n+static int add_to_index_cacheinfo(unsigned int mode,\n+\t\t\t\t  const struct object_id *oid, const char *path)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_pushl(&cp.args, \"update-index\", \"--add\", \"--cacheinfo\", NULL);\n+\targv_array_pushf(&cp.args, \"%o,%s,%s\", mode, oid_to_hex(oid), path);\n+\treturn run_command(&cp);\n+}\n+\n+static int remove_from_index(const char *path)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_pushl(&cp.args, \"update-index\", \"--remove\", \"--\", path, NULL);\n+\treturn run_command(&cp);\n+}\n+\n+static int checkout_from_index(const char *path)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_pushl(&cp.args, \"checkout-index\", \"-u\", \"-f\", \"--\", path, NULL);\n+\treturn run_command(&cp);\n+}\n+\n+static int merge_one_file_deleted(const struct object_id *orig_blob,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode)) {\n+\t\tfprintf(stderr, \"ERROR: File %s deleted on one branch but had its\\n\", path);\n+\t\tfprintf(stderr, \"ERROR: permissions changed on the other.\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tif (our_blob) {\n+\t\tprintf(\"Removing %s\\n\", path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\treturn remove_from_index(path);\n+}\n+\n+static int do_merge_one_file(const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, source, dest;\n+\tstruct strbuf src1 = STRBUF_INIT, src2 = STRBUF_INIT, orig = STRBUF_INIT;\n+\tstruct child_process cp_merge = CHILD_PROCESS_INIT,\n+\t\tcp_checkout = CHILD_PROCESS_INIT,\n+\t\tcp_update = CHILD_PROCESS_INIT;\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK) {\n+\t\tfprintf(stderr, \"ERROR: %s: Not merging symbolic link changes.\\n\", path);\n+\t\treturn 1;\n+\t} else if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK) {\n+\t\tfprintf(stderr, \"ERROR: %s: Not merging conflicting submodule changes.\\n\",\n+\t\t\tpath);\n+\t\treturn 1;\n+\t}\n+\n+\tcreate_temp_file(our_blob, &src1);\n+\tcreate_temp_file(their_blob, &src2);\n+\n+\tif (orig_blob) {\n+\t\tprintf(\"Auto-merging %s\\n\", path);\n+\t\tcreate_temp_file(orig_blob, &orig);\n+\t} else {\n+\t\tprintf(\"Added %s in both, but differently.\\n\", path);\n+\t\tcreate_temp_file(the_hash_algo->empty_blob, &orig);\n+\t}\n+\n+\tcp_merge.git_cmd = 1;\n+\targv_array_pushl(&cp_merge.args, \"merge-file\", src1.buf, orig.buf, src2.buf,\n+\t\t\t NULL);\n+\tret = run_command(&cp_merge);\n+\n+\tif (ret != 0)\n+\t\tret = 1;\n+\n+\tcp_checkout.git_cmd = 1;\n+\targv_array_pushl(&cp_checkout.args, \"checkout-index\", \"-f\", \"--stage=2\",\n+\t\t\t \"--\", path, NULL);\n+\tif (run_command(&cp_checkout))\n+\t\treturn 1;\n+\n+\tsource = open(src1.buf, O_RDONLY);\n+\tdest = open(path, O_WRONLY | O_TRUNC);\n+\n+\tcopy_fd(source, dest);\n+\n+\tclose(source);\n+\tclose(dest);\n+\n+\tunlink(orig.buf);\n+\tunlink(src1.buf);\n+\tunlink(src2.buf);\n+\n+\tstrbuf_release(&src1);\n+\tstrbuf_release(&src2);\n+\tstrbuf_release(&orig);\n+\n+\tif (ret) {\n+\t\tfprintf(stderr, \"ERROR: \");\n+\n+\t\tif (!orig_blob) {\n+\t\t\tfprintf(stderr, \"content conflict\");\n+\t\t\tif (our_mode != their_mode)\n+\t\t\t\tfprintf(stderr, \", \");\n+\t\t}\n+\n+\t\tif (our_mode != their_mode)\n+\t\t\tfprintf(stderr, \"permissions conflict: %o->%o,%o\",\n+\t\t\t\torig_mode, our_mode, their_mode);\n+\n+\t\tfprintf(stderr, \" in %s\\n\", path);\n+\n+\t\treturn 1;\n+\t}\n+\n+\tcp_update.git_cmd = 1;\n+\targv_array_pushl(&cp_update.args, \"update-index\", \"--\", path, NULL);\n+\treturn run_command(&cp_update);\n+}\n+\n+static int merge_one_file(const struct object_id *orig_blob,\n+\t\t\t  const struct object_id *our_blob,\n+\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (their_blob && oideq(orig_blob, their_blob))))\n+\t\treturn merge_one_file_deleted(orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\telse if (!orig_blob && our_blob && !their_blob) {\n+\t\treturn add_to_index_cacheinfo(our_mode, our_blob, path);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tprintf(\"Adding %s\\n\", path);\n+\n+\t\tif (file_exists(path)) {\n+\t\t\tfprintf(stderr, \"ERROR: untracked %s is overwritten by the merge.\\n\", path);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tif (add_to_index_cacheinfo(their_mode, their_blob, path))\n+\t\t\treturn 1;\n+\t\treturn checkout_from_index(path);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\tif (our_mode != their_mode) {\n+\t\t\tfprintf(stderr, \"ERROR: File %s added identically in both branches,\", path);\n+\t\t\tfprintf(stderr, \"ERROR: but permissions conflict %o->%o.\\n\",\n+\t\t\t\tour_mode, their_mode);\n+\t\t\treturn 1;\n+\t\t}\n+\n+\t\tprintf(\"Adding %s\\n\", path);\n+\n+\t\tif (add_to_index_cacheinfo(our_mode, our_blob, path))\n+\t\t\treturn 1;\n+\t\treturn checkout_from_index(path);\n+\t} else if (our_blob && their_blob)\n+\t\treturn do_merge_one_file(orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\telse {\n+\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n+\n+\t\tif (orig_blob)\n+\t\t\torig_hex = oid_to_hex(orig_blob);\n+\t\tif (our_blob)\n+\t\t\tour_hex = oid_to_hex(our_blob);\n+\t\tif (their_blob)\n+\t\t\ttheir_hex = oid_to_hex(their_blob);\n+\n+\t\tfprintf(stderr, \"ERROR: %s: Not handling case %s -> %s -> %s\\n\",\n+\t\t\tpath, orig_hex, our_hex, their_hex);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (!get_oid(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\torig_mode = strtol(argv[5], NULL, 8);\n+\t}\n+\n+\tif (!get_oid(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tour_mode = strtol(argv[6], NULL, 8);\n+\t}\n+\n+\tif (!get_oid(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\ttheir_mode = strtol(argv[7], NULL, 8);\n+\t}\n+\n+\treturn merge_one_file(p_orig_blob, p_our_blob, p_their_blob, argv[4],\n+\t\t\t      orig_mode, our_mode, their_mode);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex a2d337eed7..058d91a2a5 100644\n--- a/git.c\n+++ b/git.c\n@@ -532,6 +532,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh\nindex 2eddcc7664..5fb74e39a0 100755\n--- a/t/t6035-merge-dir-to-symlink.sh\n+++ b/t/t6035-merge-dir-to-symlink.sh\n@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400589","messageId":"20200625121953.16991-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 05/17] merge-one-file: libify merge_one_file()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:41Z","receivedAt":"2020-06-25T12:49:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves merge_one_file() (and its helper functions) to a new file,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.  It is also renamed\nmerge_strategies_one_file().\n\nThis is not a faithful copy-and-paste; in the builtin versions,\nmerge_one_file() operated on `the_repository' and `the_index', something\nwe cannot allow a function part of libgit.a to do.  Hence, it now takes\na pointer to a repository as its first argument (and helper functions\ntakes a pointer to an `index_state').\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    This patch is best viewed with `--color-moved'.\n\n Makefile                 |   1 +\n builtin/merge-one-file.c | 190 +-------------------------------------\n merge-strategies.c       | 191 +++++++++++++++++++++++++++++++++++++++\n merge-strategies.h       |  13 +++\n 4 files changed, 209 insertions(+), 186 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex 19574f5133..1ab4d160cb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -911,6 +911,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nindex d612885723..2f7a3e1db2 100644\n--- a/builtin/merge-one-file.c\n+++ b/builtin/merge-one-file.c\n@@ -23,191 +23,8 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"cache.h\"\n #include \"builtin.h\"\n-#include \"commit.h\"\n-#include \"dir.h\"\n #include \"lockfile.h\"\n-#include \"object-store.h\"\n-#include \"xdiff-interface.h\"\n-\n-static int add_to_index_cacheinfo(unsigned int mode,\n-\t\t\t\t  const struct object_id *oid, const char *path)\n-{\n-\tstruct cache_entry *ce;\n-\tint len, option;\n-\n-\tif (!verify_path(path, mode))\n-\t\treturn error(\"Invalid path '%s'\", path);\n-\n-\tlen = strlen(path);\n-\tce = make_empty_cache_entry(&the_index, len);\n-\n-\toidcpy(&ce->oid, oid);\n-\tmemcpy(ce->name, path, len);\n-\tce->ce_flags = create_ce_flags(0);\n-\tce->ce_namelen = len;\n-\tce->ce_mode = create_ce_mode(mode);\n-\tif (assume_unchanged)\n-\t\tce->ce_flags |= CE_VALID;\n-\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n-\tif (add_cache_entry(ce, option))\n-\t\treturn error(\"%s: cannot add to the index\", path);\n-\n-\treturn 0;\n-}\n-\n-static int checkout_from_index(const char *path)\n-{\n-\tstruct checkout state;\n-\tstruct cache_entry *ce;\n-\n-\tstate.istate = &the_index;\n-\tstate.force = 1;\n-\tstate.base_dir = \"\";\n-\tstate.base_dir_len = 0;\n-\n-\tce = cache_file_exists(path, strlen(path), 0);\n-\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n-\t\treturn error(\"%s: cannot checkout file\", path);\n-\treturn 0;\n-}\n-\n-static int merge_one_file_deleted(const struct object_id *orig_blob,\n-\t\t\t\t  const struct object_id *our_blob,\n-\t\t\t\t  const struct object_id *their_blob, const char *path,\n-\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n-{\n-\tif ((our_blob && orig_mode != our_mode) ||\n-\t    (their_blob && orig_mode != their_mode))\n-\t\treturn error(_(\"File %s deleted on one branch but had its \"\n-\t\t\t       \"permissions changed on the other.\"), path);\n-\n-\tif (our_blob) {\n-\t\tprintf(\"Removing %s\\n\", path);\n-\n-\t\tif (file_exists(path))\n-\t\t\tremove_path(path);\n-\t}\n-\n-\tif (remove_file_from_cache(path))\n-\t\treturn error(\"%s: cannot remove from the index\", path);\n-\treturn 0;\n-}\n-\n-static int do_merge_one_file(const struct object_id *orig_blob,\n-\t\t\t     const struct object_id *our_blob,\n-\t\t\t     const struct object_id *their_blob, const char *path,\n-\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n-{\n-\tint ret, i, dest;\n-\tmmbuffer_t result = {NULL, 0};\n-\tmmfile_t mmfs[3];\n-\txmparam_t xmp = {{0}};\n-\tstruct cache_entry *ce;\n-\n-\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n-\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n-\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n-\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n-\n-\tread_mmblob(mmfs + 0, our_blob);\n-\tread_mmblob(mmfs + 2, their_blob);\n-\n-\tif (orig_blob) {\n-\t\tprintf(\"Auto-merging %s\\n\", path);\n-\t\tread_mmblob(mmfs + 1, orig_blob);\n-\t} else {\n-\t\tprintf(\"Added %s in both, but differently.\\n\", path);\n-\t\tread_mmblob(mmfs + 1, the_hash_algo->empty_blob);\n-\t}\n-\n-\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n-\txmp.style = 0;\n-\txmp.favor = 0;\n-\n-\tret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result);\n-\n-\tfor (i = 0; i < 3; i++)\n-\t\tfree(mmfs[i].ptr);\n-\n-\tif (ret > 127)\n-\t\tret = 1;\n-\n-\tce = cache_file_exists(path, strlen(path), 0);\n-\tif (!ce)\n-\t\tBUG(\"file is not present in the cache?\");\n-\n-\tunlink(path);\n-\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n-\twrite_in_full(dest, result.ptr, result.size);\n-\tclose(dest);\n-\n-\tfree(result.ptr);\n-\n-\tif (ret) {\n-\t\tif (!orig_blob)\n-\t\t\terror(_(\"content conflict in %s\"), path);\n-\t\tif (our_mode != their_mode)\n-\t\t\terror(_(\"permission conflict: %o->%o,%o in %s\"),\n-\t\t\t      orig_mode, our_mode, their_mode, path);\n-\n-\t\treturn 1;\n-\t}\n-\n-\treturn add_file_to_cache(path, 0);\n-}\n-\n-static int merge_one_file(const struct object_id *orig_blob,\n-\t\t\t  const struct object_id *our_blob,\n-\t\t\t  const struct object_id *their_blob, const char *path,\n-\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n-{\n-\tif (orig_blob &&\n-\t    ((our_blob && oideq(orig_blob, our_blob)) ||\n-\t     (their_blob && oideq(orig_blob, their_blob))))\n-\t\treturn merge_one_file_deleted(orig_blob, our_blob, their_blob, path,\n-\t\t\t\t\t      orig_mode, our_mode, their_mode);\n-\telse if (!orig_blob && our_blob && !their_blob) {\n-\t\treturn add_to_index_cacheinfo(our_mode, our_blob, path);\n-\t} else if (!orig_blob && !our_blob && their_blob) {\n-\t\tprintf(\"Adding %s\\n\", path);\n-\n-\t\tif (file_exists(path))\n-\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n-\n-\t\tif (add_to_index_cacheinfo(their_mode, their_blob, path))\n-\t\t\treturn 1;\n-\t\treturn checkout_from_index(path);\n-\t} else if (!orig_blob && our_blob && their_blob &&\n-\t\t   oideq(our_blob, their_blob)) {\n-\t\tif (our_mode != their_mode)\n-\t\t\treturn error(_(\"File %s added identically in both branches, \"\n-\t\t\t\t       \"but permissions conflict %o->%o.\"),\n-\t\t\t\t     path, our_mode, their_mode);\n-\n-\t\tprintf(\"Adding %s\\n\", path);\n-\n-\t\tif (add_to_index_cacheinfo(our_mode, our_blob, path))\n-\t\t\treturn 1;\n-\t\treturn checkout_from_index(path);\n-\t} else if (our_blob && their_blob)\n-\t\treturn do_merge_one_file(orig_blob, our_blob, their_blob, path,\n-\t\t\t\t\t orig_mode, our_mode, their_mode);\n-\telse {\n-\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n-\n-\t\tif (orig_blob)\n-\t\t\torig_hex = oid_to_hex(orig_blob);\n-\t\tif (our_blob)\n-\t\t\tour_hex = oid_to_hex(our_blob);\n-\t\tif (their_blob)\n-\t\t\ttheir_hex = oid_to_hex(their_blob);\n-\n-\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n-\t\t\tpath, orig_hex, our_hex, their_hex);\n-\t}\n-\n-\treturn 0;\n-}\n+#include \"merge-strategies.h\"\n \n static const char builtin_merge_one_file_usage[] =\n \t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n@@ -244,8 +61,9 @@ int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n \t\ttheir_mode = strtol(argv[7], NULL, 8);\n \t}\n \n-\tret = merge_one_file(p_orig_blob, p_our_blob, p_their_blob, argv[4],\n-\t\t\t     orig_mode, our_mode, their_mode);\n+\tret = merge_strategies_one_file(the_repository,\n+\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n+\t\t\t\t\torig_mode, our_mode, their_mode);\n \n \tif (ret) {\n \t\trollback_lock_file(&lock);\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..3a9fce9f22\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,191 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int add_to_index_cacheinfo(struct index_state *istate,\n+\t\t\t\t  unsigned int mode,\n+\t\t\t\t  const struct object_id *oid, const char *path)\n+{\n+\tstruct cache_entry *ce;\n+\tint len, option;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(0);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n+\tif (add_index_entry(istate, ce, option))\n+\t\treturn error(_(\"%s: cannot add to the index\"), path);\n+\n+\treturn 0;\n+}\n+\n+static int checkout_from_index(struct index_state *istate, const char *path)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\tstruct cache_entry *ce;\n+\n+\tstate.istate = istate;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tce = index_file_exists(istate, path, strlen(path), 0);\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *orig_blob,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(\"Removing %s\\n\", path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\tstruct cache_entry *ce;\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\n+\tread_mmblob(mmfs + 0, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\tif (orig_blob) {\n+\t\tprintf(\"Auto-merging %s\\n\", path);\n+\t\tread_mmblob(mmfs + 1, orig_blob);\n+\t} else {\n+\t\tprintf(\"Added %s in both, but differently.\\n\", path);\n+\t\tread_mmblob(mmfs + 1, &null_oid);\n+\t}\n+\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n+\n+\tret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret > 127)\n+\t\tret = 1;\n+\n+\tce = index_file_exists(istate, path, strlen(path), 0);\n+\tif (!ce)\n+\t\tBUG(\"file is not present in the cache?\");\n+\n+\tunlink(path);\n+\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n+\twrite_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (ret) {\n+\t\tif (!orig_blob)\n+\t\t\terror(_(\"content conflict in %s\"), path);\n+\t\tif (our_mode != their_mode)\n+\t\t\terror(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t      orig_mode, our_mode, their_mode, path);\n+\n+\t\treturn 1;\n+\t}\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_strategies_one_file(struct repository *r,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob, const char *path,\n+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t      unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (their_blob && oideq(orig_blob, their_blob))))\n+\t\treturn merge_one_file_deleted(r->index,\n+\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\telse if (!orig_blob && our_blob && !their_blob) {\n+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tprintf(\"Adding %s\\n\", path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n+\t\t\treturn 1;\n+\t\treturn checkout_from_index(r->index, path);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(\"Adding %s\\n\", path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n+\t\t\treturn 1;\n+\t\treturn checkout_from_index(r->index, path);\n+\t} else if (our_blob && their_blob)\n+\t\treturn do_merge_one_file(r->index,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\telse {\n+\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n+\n+\t\tif (orig_blob)\n+\t\t\torig_hex = oid_to_hex(orig_blob);\n+\t\tif (our_blob)\n+\t\t\tour_hex = oid_to_hex(our_blob);\n+\t\tif (their_blob)\n+\t\t\ttheir_hex = oid_to_hex(their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\tpath, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..b527d145c7\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,13 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+int merge_strategies_one_file(struct repository *r,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob, const char *path,\n+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t      unsigned int their_mode);\n+\n+#endif /* MERGE_STRATEGIES_H */\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400590","messageId":"20200625121953.16991-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 06/17] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:42Z","receivedAt":"2020-06-25T12:49:09Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves merge_one_path(), merge_all(), and their\nhelpers to merge-strategies.c.  They also take a callback to dictate\nwhat they should do for each file.  For now, only one launching a new\nprocess is defined to preserve the behaviour of the builtin version.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    This patch is best viewed with `--color-moved'.\n\n builtin/merge-index.c | 77 +++------------------------------\n merge-strategies.c    | 99 +++++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    | 17 ++++++++\n 3 files changed, 123 insertions(+), 70 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..6cb666cc78 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,11 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n-#include \"run-command.h\"\n-\n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n-\n-static int merge_entry(int pos, const char *path)\n-{\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n-\n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n-\n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n-\t}\n-}\n+#include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tconst char *pgm;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t      merge_program_cb, (void *)pgm);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 3a9fce9f22..f4c0b4acd6 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"merge-strategies.h\"\n+#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n static int add_to_index_cacheinfo(struct index_state *istate,\n@@ -189,3 +190,101 @@ int merge_strategies_one_file(struct repository *r,\n \n \treturn 0;\n }\n+\n+int merge_program_cb(const struct object_id *orig_blob,\n+\t\t     const struct object_id *our_blob,\n+\t\t     const struct object_id *their_blob, const char *path,\n+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t     void *data)\n+{\n+\tchar ownbuf[3][60] = {{0}};\n+\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n+\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n+\t\t\t\t    NULL };\n+\n+\tif (orig_blob)\n+\t\targuments[1] = oid_to_hex(orig_blob);\n+\tif (our_blob)\n+\t\targuments[2] = oid_to_hex(our_blob);\n+\tif (their_blob)\n+\t\targuments[3] = oid_to_hex(their_blob);\n+\n+\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n+\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n+\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n+\n+\treturn run_command_v_opt(arguments, 0);\n+}\n+\n+static int merge_entry(struct index_state *istate, int quiet, int pos,\n+\t\t       const char *path, merge_cb cb, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\treturn -2;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t   const char *path, merge_cb cb, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all(struct index_state *istate, int oneshot, int quiet,\n+\t      merge_cb cb, void *data)\n+{\n+\tint err = 0, i, ret;\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2) {\n+\t\t\tif (oneshot)\n+\t\t\t\terr++;\n+\t\t\telse\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex b527d145c7..cf78d7eaf4 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n \t\t\t      unsigned int their_mode);\n \n+typedef int (*merge_cb)(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_program_cb(const struct object_id *orig_blob,\n+\t\t     const struct object_id *our_blob,\n+\t\t     const struct object_id *their_blob, const char *path,\n+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t     void *data);\n+\n+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t   const char *path, merge_cb cb, void *data);\n+int merge_all(struct index_state *istate, int oneshot, int quiet,\n+\t      merge_cb cb, void *data);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400591","messageId":"20200625121953.16991-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 09/17] merge-resolve: libify merge_resolve()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:45Z","receivedAt":"2020-06-25T12:49:12Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves merge_resolve() (and its helper functions) to\nmerge-strategies.c.  This will enable `git merge' and the sequencer to\ndirectly call it instead of forking.\n\nHere too, this is not a faithful copy-and-paste; the new\nmerge_resolve() (renamed merge_strategies_resolve()) takes a pointer to\nthe repository, instead of using `the_repository'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    This patch is best viewed with `--color-moved'.\n\n builtin/merge-resolve.c | 86 +----------------------------------------\n merge-strategies.c      | 85 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 3 files changed, 91 insertions(+), 85 deletions(-)\n\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nindex 2c364fcdb0..59f734473b 100644\n--- a/builtin/merge-resolve.c\n+++ b/builtin/merge-resolve.c\n@@ -10,92 +10,8 @@\n  */\n \n #include \"cache.h\"\n-#include \"cache-tree.h\"\n #include \"builtin.h\"\n-#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n-#include \"unpack-trees.h\"\n-\n-static int add_tree(const struct object_id *oid, struct tree_desc *t)\n-{\n-\tstruct tree *tree;\n-\n-\ttree = parse_tree_indirect(oid);\n-\tif (parse_tree(tree))\n-\t\treturn -1;\n-\n-\tinit_tree_desc(t, tree->buffer, tree->size);\n-\treturn 0;\n-}\n-\n-static int merge_resolve(struct commit_list *bases, const char *head_arg,\n-\t\t\t struct commit_list *remote)\n-{\n-\tint i = 0;\n-\tstruct lock_file lock = LOCK_INIT;\n-\tstruct tree_desc t[MAX_UNPACK_TREES];\n-\tstruct unpack_trees_options opts;\n-\tstruct object_id head, oid;\n-\tstruct commit_list *j;\n-\n-\tif (head_arg)\n-\t\tget_oid(head_arg, &head);\n-\n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\trefresh_index(the_repository->index, 0, NULL, NULL, NULL);\n-\n-\tmemset(&opts, 0, sizeof(opts));\n-\topts.head_idx = 1;\n-\topts.src_index = the_repository->index;\n-\topts.dst_index = the_repository->index;\n-\topts.update = 1;\n-\topts.merge = 1;\n-\topts.aggressive = 1;\n-\n-\tfor (j = bases; j; j = j->next) {\n-\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n-\t\t\tgoto out;\n-\t}\n-\n-\tif (head_arg && add_tree(&head, t + (i++)))\n-\t\tgoto out;\n-\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n-\t\tgoto out;\n-\n-\tif (i == 1)\n-\t\topts.fn = oneway_merge;\n-\telse if (i == 2) {\n-\t\topts.fn = twoway_merge;\n-\t\topts.initial_checkout = is_index_unborn(the_repository->index);\n-\t} else if (i >= 3) {\n-\t\topts.fn = threeway_merge;\n-\t\topts.head_idx = i - 1;\n-\t}\n-\n-\tif (unpack_trees(i, t, &opts))\n-\t\tgoto out;\n-\n-\tputs(\"Trying simple merge.\");\n-\twrite_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n-\n-\tif (write_index_as_tree(&oid, the_repository->index,\n-\t\t\t\tthe_repository->index_file, 0, NULL)) {\n-\t\tint ret;\n-\n-\t\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\t\tret = merge_all(the_repository->index, 0, 0,\n-\t\t\t\tmerge_one_file_cb, the_repository);\n-\n-\t\twrite_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n-\t\treturn !!ret;\n-\t}\n-\n-\treturn 0;\n-\n- out:\n-\trollback_lock_file(&lock);\n-\treturn 2;\n-}\n \n static const char builtin_merge_resolve_usage[] =\n \t\"git merge-resolve <bases>... -- <head> <remote>\";\n@@ -149,5 +65,5 @@ int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n \tif (is_baseless)\n \t\treturn 2;\n \n-\treturn merge_resolve(bases, head, remote);\n+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 39bfa1af7b..a12c575590 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,7 +1,10 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int add_to_index_cacheinfo(struct index_state *istate,\n@@ -299,3 +302,85 @@ int merge_all(struct index_state *istate, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int add_tree(const struct object_id *oid, struct tree_desc *t)\n+{\n+\tstruct tree *tree;\n+\n+\ttree = parse_tree_indirect(oid);\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tint i = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct object_id head, oid;\n+\tstruct commit_list *j;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\trefresh_index(r->index, 0, NULL, NULL, NULL);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.update = 1;\n+\topts.merge = 1;\n+\topts.aggressive = 1;\n+\n+\tfor (j = bases; j && j->item; j = j->next) {\n+\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n+\t\t\tgoto out;\n+\t}\n+\n+\tif (head_arg && add_tree(&head, t + (i++)))\n+\t\tgoto out;\n+\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n+\t\tgoto out;\n+\n+\tif (i == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (i == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (i >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = i - 1;\n+\t}\n+\n+\tif (unpack_trees(i, t, &opts))\n+\t\tgoto out;\n+\n+\tputs(\"Trying simple merge.\");\n+\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\n+\t\tputs(\"Simple merge failed, trying Automatic merge.\");\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all(r->index, 0, 0, merge_one_file_cb, r);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+\n+ out:\n+\trollback_lock_file(&lock);\n+\treturn 2;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 40e175ca39..778f8ce9d6 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_strategies_one_file(struct repository *r,\n@@ -33,4 +34,8 @@ int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all(struct index_state *istate, int oneshot, int quiet,\n \t      merge_cb cb, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400592","messageId":"20200625121953.16991-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 08/17] merge-resolve: remove calls to external processes","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:44Z","receivedAt":"2020-06-25T12:49:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This removes calls to external processes to avoid reading and writing\nthe index over and over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all() function.  A callback\n   function, merge_one_file_cb(), is added to allow it to call\n   merge_one_file() without forking.\n\nHere too, the index is read in cmd_merge_resolve(), but merge_resolve()\ntakes care of writing it back to the disk.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-resolve.c | 103 ++++++++++++++++++++++++++++------------\n merge-strategies.c      |  11 +++++\n merge-strategies.h      |   6 +++\n 3 files changed, 89 insertions(+), 31 deletions(-)\n\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nindex c66fef7b7f..2c364fcdb0 100644\n--- a/builtin/merge-resolve.c\n+++ b/builtin/merge-resolve.c\n@@ -10,54 +10,91 @@\n  */\n \n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"builtin.h\"\n-#include \"run-command.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+#include \"unpack-trees.h\"\n+\n+static int add_tree(const struct object_id *oid, struct tree_desc *t)\n+{\n+\tstruct tree *tree;\n+\n+\ttree = parse_tree_indirect(oid);\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n \n static int merge_resolve(struct commit_list *bases, const char *head_arg,\n \t\t\t struct commit_list *remote)\n {\n+\tint i = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct object_id head, oid;\n \tstruct commit_list *j;\n-\tstruct child_process cp_update = CHILD_PROCESS_INIT,\n-\t\tcp_read = CHILD_PROCESS_INIT,\n-\t\tcp_write = CHILD_PROCESS_INIT;\n-\n-\tcp_update.git_cmd = 1;\n-\targv_array_pushl(&cp_update.args, \"update-index\", \"-q\", \"--refresh\", NULL);\n-\trun_command(&cp_update);\n-\n-\tcp_read.git_cmd = 1;\n-\targv_array_pushl(&cp_read.args, \"read-tree\", \"-u\", \"-m\", \"--aggressive\", NULL);\n-\n-\tfor (j = bases; j && j->item; j = j->next)\n-\t\targv_array_push(&cp_read.args, oid_to_hex(&j->item->object.oid));\n \n \tif (head_arg)\n-\t\targv_array_push(&cp_read.args, head_arg);\n-\tif (remote && remote->item)\n-\t\targv_array_push(&cp_read.args, oid_to_hex(&remote->item->object.oid));\n+\t\tget_oid(head_arg, &head);\n \n-\tif (run_command(&cp_read))\n-\t\treturn 2;\n+\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\trefresh_index(the_repository->index, 0, NULL, NULL, NULL);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = the_repository->index;\n+\topts.dst_index = the_repository->index;\n+\topts.update = 1;\n+\topts.merge = 1;\n+\topts.aggressive = 1;\n+\n+\tfor (j = bases; j; j = j->next) {\n+\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n+\t\t\tgoto out;\n+\t}\n+\n+\tif (head_arg && add_tree(&head, t + (i++)))\n+\t\tgoto out;\n+\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n+\t\tgoto out;\n+\n+\tif (i == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (i == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(the_repository->index);\n+\t} else if (i >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = i - 1;\n+\t}\n+\n+\tif (unpack_trees(i, t, &opts))\n+\t\tgoto out;\n \n \tputs(\"Trying simple merge.\");\n+\twrite_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n \n-\tcp_write.git_cmd = 1;\n-\tcp_write.no_stdout = 1;\n-\tcp_write.no_stderr = 1;\n-\targv_array_push(&cp_write.args, \"write-tree\");\n-\tif (run_command(&cp_write)) {\n-\t\tstruct child_process cp_merge = CHILD_PROCESS_INIT;\n+\tif (write_index_as_tree(&oid, the_repository->index,\n+\t\t\t\tthe_repository->index_file, 0, NULL)) {\n+\t\tint ret;\n \n-\t\tputs(\"Simple merge failed, trying Automatic merge.\");\n+\t\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all(the_repository->index, 0, 0,\n+\t\t\t\tmerge_one_file_cb, the_repository);\n \n-\t\tcp_merge.git_cmd = 1;\n-\t\targv_array_pushl(&cp_merge.args, \"merge-index\", \"-o\",\n-\t\t\t\t \"git-merge-one-file\", \"-a\", NULL);\n-\t\tif (run_command(&cp_merge))\n-\t\t\treturn 1;\n+\t\twrite_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n \t}\n \n \treturn 0;\n+\n+ out:\n+\trollback_lock_file(&lock);\n+\treturn 2;\n }\n \n static const char builtin_merge_resolve_usage[] =\n@@ -73,6 +110,10 @@ int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n \tif (argc < 5)\n \t\tusage(builtin_merge_resolve_usage);\n \n+\tsetup_work_tree();\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"invalid index\");\n+\n \t/* The first parameters up to -- are merge bases; the rest are\n \t * heads. */\n \tfor (i = 1; i < argc; i++) {\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex f4c0b4acd6..39bfa1af7b 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -191,6 +191,17 @@ int merge_strategies_one_file(struct repository *r,\n \treturn 0;\n }\n \n+int merge_one_file_cb(const struct object_id *orig_blob,\n+\t\t      const struct object_id *our_blob,\n+\t\t      const struct object_id *their_blob, const char *path,\n+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t      void *data)\n+{\n+\treturn merge_strategies_one_file((struct repository *)data,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+}\n+\n int merge_program_cb(const struct object_id *orig_blob,\n \t\t     const struct object_id *our_blob,\n \t\t     const struct object_id *their_blob, const char *path,\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex cf78d7eaf4..40e175ca39 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -16,6 +16,12 @@ typedef int (*merge_cb)(const struct object_id *orig_blob,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_cb(const struct object_id *orig_blob,\n+\t\t      const struct object_id *our_blob,\n+\t\t      const struct object_id *their_blob, const char *path,\n+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t      void *data);\n+\n int merge_program_cb(const struct object_id *orig_blob,\n \t\t     const struct object_id *our_blob,\n \t\t     const struct object_id *their_blob, const char *path,\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400593","messageId":"20200625121953.16991-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 07/17] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:43Z","receivedAt":"2020-06-25T12:49:15Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port keeps using external processes for operations\non the index, or to call `git merge-one-file'.  This will be addressed\nin the next two commits.\n\nThe parameters of merge_resolve() will be surprising at first glance:\nwhy using a commit list for `bases' and `remote', where we could use an\noid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_resolve() directly, and\nit already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_resolve() takes the same types of parameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-resolve.c | 112 ++++++++++++++++++++++++++++++++++++++++\n git-merge-resolve.sh    |  54 -------------------\n git.c                   |   1 +\n 5 files changed, 115 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex 1ab4d160cb..ccea651ac8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -596,7 +596,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1092,6 +1091,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 9205d5ecdc..6ea207c9fd 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -174,6 +174,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..c66fef7b7f\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,112 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+\n+static int merge_resolve(struct commit_list *bases, const char *head_arg,\n+\t\t\t struct commit_list *remote)\n+{\n+\tstruct commit_list *j;\n+\tstruct child_process cp_update = CHILD_PROCESS_INIT,\n+\t\tcp_read = CHILD_PROCESS_INIT,\n+\t\tcp_write = CHILD_PROCESS_INIT;\n+\n+\tcp_update.git_cmd = 1;\n+\targv_array_pushl(&cp_update.args, \"update-index\", \"-q\", \"--refresh\", NULL);\n+\trun_command(&cp_update);\n+\n+\tcp_read.git_cmd = 1;\n+\targv_array_pushl(&cp_read.args, \"read-tree\", \"-u\", \"-m\", \"--aggressive\", NULL);\n+\n+\tfor (j = bases; j && j->item; j = j->next)\n+\t\targv_array_push(&cp_read.args, oid_to_hex(&j->item->object.oid));\n+\n+\tif (head_arg)\n+\t\targv_array_push(&cp_read.args, head_arg);\n+\tif (remote && remote->item)\n+\t\targv_array_push(&cp_read.args, oid_to_hex(&remote->item->object.oid));\n+\n+\tif (run_command(&cp_read))\n+\t\treturn 2;\n+\n+\tputs(\"Trying simple merge.\");\n+\n+\tcp_write.git_cmd = 1;\n+\tcp_write.no_stdout = 1;\n+\tcp_write.no_stderr = 1;\n+\targv_array_push(&cp_write.args, \"write-tree\");\n+\tif (run_command(&cp_write)) {\n+\t\tstruct child_process cp_merge = CHILD_PROCESS_INIT;\n+\n+\t\tputs(\"Simple merge failed, trying Automatic merge.\");\n+\n+\t\tcp_merge.git_cmd = 1;\n+\t\targv_array_pushl(&cp_merge.args, \"merge-index\", \"-o\",\n+\t\t\t\t \"git-merge-one-file\", \"-a\", NULL);\n+\t\tif (run_command(&cp_merge))\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, is_baseless = 1, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\t/* The first parameters up to -- are merge bases; the rest are\n+\t * heads. */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse if (remote) {\n+\t\t\t/* Give up if we are given two or more remotes.\n+\t\t\t * Not handling octopus. */\n+\t\t\treturn 2;\n+\t\t} else {\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tget_oid(argv[i], &oid);\n+\t\t\tis_baseless &= sep_seen;\n+\n+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\t\tif (sep_seen)\n+\t\t\t\t\tcommit_list_append(commit, &remote);\n+\t\t\t\telse\n+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (is_baseless)\n+\t\treturn 2;\n+\n+\treturn merge_resolve(bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 343fe7bccd..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex 058d91a2a5..2e92019493 100644\n--- a/git.c\n+++ b/git.c\n@@ -536,6 +536,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400594","messageId":"20200625121953.16991-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 10/17] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:46Z","receivedAt":"2020-06-25T12:49:16Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"get_better_branch_name() will be used by rebase-octopus once it is\nrewritten in C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    This patch is best viewed with `--color-moved'.\n\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex 0f0485ecfe..bbbd8e352d 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1915,7 +1915,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex aa36de2f64..5f3d05268f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -108,3 +108,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400595","messageId":"20200625121953.16991-14-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 13/17] merge-octopus: libify merge_octopus()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:49Z","receivedAt":"2020-06-25T12:49:17Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves merge_octopus() (and its helper functions) to\nmerge-strategies.c.  This will enable `git merge' and the sequencer to\ndirectly call it instead of forking.\n\nOnce again, this is not a faithful copy-and-paste; the new\nmerge_octopus() (renamed merge_strategies_octopus()) takes a pointer to\nthe repository, instead of using `the_repository'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    This patch is best viewed with `--color-moved'.\n\n builtin/merge-octopus.c | 197 +---------------------------------------\n merge-strategies.c      | 191 ++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 3 files changed, 196 insertions(+), 195 deletions(-)\n\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nindex 14310a4eb1..37bbdf11cc 100644\n--- a/builtin/merge-octopus.c\n+++ b/builtin/merge-octopus.c\n@@ -9,202 +9,9 @@\n  */\n \n #include \"cache.h\"\n-#include \"cache-tree.h\"\n #include \"builtin.h\"\n-#include \"commit-reach.h\"\n-#include \"lockfile.h\"\n+#include \"commit.h\"\n #include \"merge-strategies.h\"\n-#include \"unpack-trees.h\"\n-\n-static int fast_forward(const struct object_id *oids, int nr, int aggressive)\n-{\n-\tint i;\n-\tstruct tree_desc t[MAX_UNPACK_TREES];\n-\tstruct unpack_trees_options opts;\n-\tstruct lock_file lock = LOCK_INIT;\n-\n-\trepo_read_index_preload(the_repository, NULL, 0);\n-\tif (refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL))\n-\t\treturn -1;\n-\n-\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\n-\tmemset(&opts, 0, sizeof(opts));\n-\topts.head_idx = 1;\n-\topts.src_index = the_repository->index;\n-\topts.dst_index = the_repository->index;\n-\topts.merge = 1;\n-\topts.update = 1;\n-\topts.aggressive = aggressive;\n-\n-\tfor (i = 0; i < nr; i++) {\n-\t\tstruct tree *tree;\n-\t\ttree = parse_tree_indirect(oids + i);\n-\t\tif (parse_tree(tree))\n-\t\t\treturn -1;\n-\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n-\t}\n-\n-\tif (nr == 1)\n-\t\topts.fn = oneway_merge;\n-\telse if (nr == 2) {\n-\t\topts.fn = twoway_merge;\n-\t\topts.initial_checkout = is_index_unborn(the_repository->index);\n-\t} else if (nr >= 3) {\n-\t\topts.fn = threeway_merge;\n-\t\topts.head_idx = nr - 1;\n-\t}\n-\n-\tif (unpack_trees(nr, t, &opts))\n-\t\treturn -1;\n-\n-\tif (write_locked_index(the_repository->index, &lock, COMMIT_LOCK))\n-\t\treturn error(_(\"unable to write new index file\"));\n-\n-\treturn 0;\n-}\n-\n-static int write_tree(struct tree **reference_tree)\n-{\n-\tstruct object_id oid;\n-\tint ret;\n-\n-\tret = write_index_as_tree(&oid, the_repository->index,\n-\t\t\t\t  the_repository->index_file, 0, NULL);\n-\tif (!ret)\n-\t\t*reference_tree = lookup_tree(the_repository, &oid);\n-\n-\treturn ret;\n-}\n-\n-static int merge_octopus(struct commit_list *bases, const char *head_arg,\n-\t\t\t struct commit_list *remotes)\n-{\n-\tint non_ff_merge = 0, ret = 0, references = 1;\n-\tstruct commit **reference_commit;\n-\tstruct tree *reference_tree;\n-\tstruct commit_list *j;\n-\tstruct object_id head;\n-\tstruct strbuf sb = STRBUF_INIT;\n-\n-\tget_oid(head_arg, &head);\n-\n-\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n-\treference_commit[0] = lookup_commit_reference(the_repository, &head);\n-\treference_tree = get_commit_tree(reference_commit[0]);\n-\n-\tif (repo_index_has_changes(the_repository, reference_tree, &sb)) {\n-\t\terror(_(\"Your local changes to the following files \"\n-\t\t\t\"would be overwritten by merge:\\n  %s\"),\n-\t\t      sb.buf);\n-\t\tstrbuf_release(&sb);\n-\t\tret = 2;\n-\t\tgoto out;\n-\t}\n-\n-\tfor (j = remotes; j; j = j->next) {\n-\t\tstruct commit *c = j->item;\n-\t\tstruct object_id *oid = &c->object.oid;\n-\t\tstruct commit_list *common, *k;\n-\t\tchar *branch_name;\n-\t\tint can_ff = 1;\n-\n-\t\tif (ret) {\n-\t\t\tputs(_(\"Automated merge did not work.\"));\n-\t\t\tputs(_(\"Should not be doing an octopus.\"));\n-\n-\t\t\tret = 2;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n-\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n-\n-\t\tif (!common)\n-\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n-\n-\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n-\n-\t\tif (k) {\n-\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n-\t\t\tfree(branch_name);\n-\t\t\tfree_commit_list(common);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tif (!non_ff_merge) {\n-\t\t\tint i;\n-\n-\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n-\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n-\t\t\t\t\t       &reference_commit[i]->object.oid);\n-\t\t\t}\n-\t\t}\n-\n-\t\tif (!non_ff_merge && can_ff) {\n-\t\t\tstruct object_id oids[2];\n-\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n-\n-\t\t\toidcpy(oids, &head);\n-\t\t\toidcpy(oids + 1, oid);\n-\n-\t\t\tret = fast_forward(oids, 2, 0);\n-\t\t\tif (ret) {\n-\t\t\t\tfree(branch_name);\n-\t\t\t\tfree_commit_list(common);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\treferences = 0;\n-\t\t\twrite_tree(&reference_tree);\n-\t\t} else {\n-\t\t\tint i = 0;\n-\t\t\tstruct tree *next = NULL;\n-\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n-\n-\t\t\tnon_ff_merge = 1;\n-\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n-\n-\t\t\tfor (k = common; k; k = k->next)\n-\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n-\n-\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n-\t\t\toidcpy(oids + (i++), oid);\n-\n-\t\t\tif (fast_forward(oids, i, 1)) {\n-\t\t\t\tret = 2;\n-\n-\t\t\t\tfree(branch_name);\n-\t\t\t\tfree_commit_list(common);\n-\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (write_tree(&next)) {\n-\t\t\t\tstruct lock_file lock = LOCK_INIT;\n-\n-\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n-\t\t\t\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n-\t\t\t\tret = !!merge_all(the_repository->index, 0, 0,\n-\t\t\t\t\t\t  merge_one_file_cb, the_repository);\n-\t\t\t\twrite_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n-\n-\t\t\t\twrite_tree(&next);\n-\t\t\t}\n-\n-\t\t\treference_tree = next;\n-\t\t}\n-\n-\t\treference_commit[references++] = c;\n-\n-\t\tfree(branch_name);\n-\t\tfree_commit_list(common);\n-\t}\n-\n-out:\n-\tfree(reference_commit);\n-\treturn ret;\n-}\n \n static const char builtin_merge_octopus_usage[] =\n \t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n@@ -254,5 +61,5 @@ int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n \tif (commit_list_count(remotes) < 2)\n \t\treturn 2;\n \n-\treturn merge_octopus(bases, head_arg, remotes);\n+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex a12c575590..8395c4c787 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"lockfile.h\"\n #include \"merge-strategies.h\"\n@@ -384,3 +385,193 @@ int merge_strategies_resolve(struct repository *r,\n \trollback_lock_file(&lock);\n \treturn 2;\n }\n+\n+static int fast_forward(struct repository *r, const struct object_id *oids,\n+\t\t\tint nr, int aggressive)\n+{\n+\tint i;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trepo_read_index_preload(r, NULL, 0);\n+\tif (refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL))\n+\t\treturn -1;\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct tree *tree;\n+\t\ttree = parse_tree_indirect(oids + i);\n+\t\tif (parse_tree(tree))\n+\t\t\treturn -1;\n+\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n+\t}\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL);\n+\tif (!ret)\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint non_ff_merge = 0, ret = 0, references = 1;\n+\tstruct commit **reference_commit;\n+\tstruct tree *reference_tree;\n+\tstruct commit_list *j;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n+\treference_commit[0] = lookup_commit_reference(r, &head);\n+\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n+\n+\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n+\tfor (j = remotes; j && j->item; j = j->next) {\n+\t\tstruct commit *c = j->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct commit_list *common, *k;\n+\t\tchar *branch_name;\n+\t\tint can_ff = 1;\n+\n+\t\tif (ret) {\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common)\n+\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n+\n+\t\tif (k) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!non_ff_merge) {\n+\t\t\tint i;\n+\n+\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n+\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n+\t\t\t\t\t       &reference_commit[i]->object.oid);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!non_ff_merge && can_ff) {\n+\t\t\tstruct object_id oids[2];\n+\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\t\t\toidcpy(oids, &head);\n+\t\t\toidcpy(oids + 1, oid);\n+\n+\t\t\tret = fast_forward(r, oids, 2, 0);\n+\t\t\tif (ret) {\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\treferences = 0;\n+\t\t\twrite_tree(r, &reference_tree);\n+\t\t} else {\n+\t\t\tint i = 0;\n+\t\t\tstruct tree *next = NULL;\n+\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n+\n+\t\t\tnon_ff_merge = 1;\n+\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\t\t\tfor (k = common; k; k = k->next)\n+\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n+\n+\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n+\t\t\toidcpy(oids + (i++), oid);\n+\n+\t\t\tif (fast_forward(r, oids, i, 1)) {\n+\t\t\t\tret = 2;\n+\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tif (write_tree(r, &next)) {\n+\t\t\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\t\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\t\t\tret = !!merge_all(r->index, 0, 0, merge_one_file_cb, r);\n+\t\t\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\t\t\twrite_tree(r, &next);\n+\t\t\t}\n+\n+\t\t\treference_tree = next;\n+\t\t}\n+\n+\t\treference_commit[references++] = c;\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\t}\n+\n+out:\n+\tfree(reference_commit);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 778f8ce9d6..938411a04e 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -37,5 +37,8 @@ int merge_all(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400596","messageId":"20200625121953.16991-16-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 15/17] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:51Z","receivedAt":"2020-06-25T12:49:19Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex d50b4ad6ad..53f64ddb87 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -748,6 +748,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\"))\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\telse if (!strcmp(strategy, \"octopus\"))\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400597","messageId":"20200625121953.16991-17-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 16/17] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:52Z","receivedAt":"2020-06-25T12:49:21Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex fd7701c88a..ea8dc58108 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -33,6 +33,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -1922,9 +1923,15 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400598","messageId":"20200625121953.16991-15-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 14/17] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:50Z","receivedAt":"2020-06-25T12:49:23Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 7da707bf55..d50b4ad6ad 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -744,7 +745,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n-\t} else {\n+\t} else if (!strcmp(strategy, \"resolve\"))\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n+\telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n \t\t\t\t\t common, head_arg, remoteheads);\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400599","messageId":"20200625121953.16991-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 11/17] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:47Z","receivedAt":"2020-06-25T12:49:25Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port keeps using external processes for operations on\nthe index, or to call `git merge-one-file'.  This will be addressed in\nthe next two commits.\n\nHere to, merge_octopus() takes two commit lists and a string to reduce\nfrictions when try_merge_strategies() will be modified to call it\ndirectly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c | 241 ++++++++++++++++++++++++++++++++++++++++\n git-merge-octopus.sh    | 112 -------------------\n git.c                   |   1 +\n 5 files changed, 244 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex ccea651ac8..8f45a3ec03 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -595,7 +595,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1088,6 +1087,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 6ea207c9fd..5a587ab70c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -170,6 +170,7 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..6216beaa2b\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,241 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit-reach.h\"\n+#include \"lockfile.h\"\n+#include \"run-command.h\"\n+#include \"unpack-trees.h\"\n+\n+static int write_tree(struct tree **reference_tree)\n+{\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf read_tree = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_push(&cp.args, \"write-tree\");\n+\tret = pipe_command(&cp, NULL, 0, &read_tree, 0, &err, 0);\n+\tif (err.len > 0)\n+\t\tfputs(err.buf, stderr);\n+\n+\tstrbuf_trim_trailing_newline(&read_tree);\n+\tget_oid(read_tree.buf, &oid);\n+\n+\t*reference_tree = lookup_tree(the_repository, &oid);\n+\n+\tstrbuf_release(&read_tree);\n+\tstrbuf_release(&err);\n+\tchild_process_clear(&cp);\n+\n+\treturn ret;\n+}\n+\n+static int merge_octopus(struct commit_list *bases, const char *head_arg,\n+\t\t\t struct commit_list *remotes)\n+{\n+\tint non_ff_merge = 0, ret = 0, references = 1;\n+\tstruct commit **reference_commit;\n+\tstruct tree *reference_tree;\n+\tstruct commit_list *j;\n+\tstruct object_id head;\n+\n+\tget_oid(head_arg, &head);\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n+\treference_commit[0] = lookup_commit_reference(the_repository, &head);\n+\treference_tree = get_commit_tree(reference_commit[0]);\n+\n+\tfor (j = remotes; j; j = j->next) {\n+\t\tstruct commit *c = j->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct commit_list *common, *k;\n+\t\tchar *branch_name;\n+\t\tint can_ff = 1;\n+\n+\t\tif (ret) {\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common)\n+\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n+\n+\t\tif (k) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!non_ff_merge) {\n+\t\t\tint i;\n+\n+\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n+\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n+\t\t\t\t\t       &reference_commit[i]->object.oid);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!non_ff_merge && can_ff) {\n+\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\t\t\tcp.git_cmd = 1;\n+\t\t\targv_array_pushl(&cp.args, \"read-tree\", \"-u\", \"-m\", NULL);\n+\t\t\targv_array_push(&cp.args, oid_to_hex(&head));\n+\t\t\targv_array_push(&cp.args, oid_to_hex(oid));\n+\n+\t\t\tret = run_command(&cp);\n+\t\t\tif (ret) {\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tchild_process_clear(&cp);\n+\t\t\treferences = 0;\n+\t\t\twrite_tree(&reference_tree);\n+\t\t} else {\n+\t\t\tstruct commit_list *l;\n+\t\t\tstruct tree *next = NULL;\n+\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\n+\t\t\tnon_ff_merge = 1;\n+\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\t\t\tcp.git_cmd = 1;\n+\t\t\targv_array_pushl(&cp.args, \"read-tree\", \"-u\", \"-m\", \"--aggressive\", NULL);\n+\n+\t\t\tfor (l = common; l; l = l->next)\n+\t\t\t\targv_array_push(&cp.args, oid_to_hex(&l->item->object.oid));\n+\n+\t\t\targv_array_push(&cp.args, oid_to_hex(&reference_tree->object.oid));\n+\t\t\targv_array_push(&cp.args, oid_to_hex(oid));\n+\n+\t\t\tif (run_command(&cp)) {\n+\t\t\t\tret = 2;\n+\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tchild_process_clear(&cp);\n+\n+\t\t\tif (write_tree(&next)) {\n+\t\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\n+\t\t\t\tcp.git_cmd = 1;\n+\t\t\t\targv_array_pushl(&cp.args, \"merge-index\", \"-o\",\n+\t\t\t\t\t\t \"git-merge-one-file\", \"-a\", NULL);\n+\t\t\t\tif (run_command(&cp))\n+\t\t\t\t\tret = 1;\n+\n+\t\t\t\tchild_process_clear(&cp);\n+\t\t\t\twrite_tree(&next);\n+\t\t\t}\n+\n+\t\t\treference_tree = next;\n+\t\t}\n+\n+\t\treference_commit[references++] = c;\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\t}\n+\n+out:\n+\tfree(reference_commit);\n+\treturn ret;\n+}\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\tstruct strbuf files = STRBUF_INIT;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\t/* The first parameters up to -- are merge bases; the rest are\n+\t * heads. */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tget_oid(argv[i], &oid);\n+\n+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\t\tif (sep_seen)\n+\t\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\t\telse\n+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Reject if this is not an octopus -- resolve should be used\n+\t * instead. */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\tcp.git_cmd = 1;\n+\targv_array_pushl(&cp.args, \"diff-index\", \"--cached\",\n+\t\t\t \"--name-only\", \"HEAD\", \"--\", NULL);\n+\tpipe_command(&cp, NULL, 0, &files, 0, NULL, 0);\n+\tchild_process_clear(&cp);\n+\n+\tif (files.len > 0) {\n+\t\tstruct strbuf **s, **b;\n+\n+\t\ts = strbuf_split(&files, '\\n');\n+\n+\t\tfprintf(stderr, _(\"Error: Your local changes to the following \"\n+\t\t\t\t  \"files would be overwritten by merge\\n\"));\n+\n+\t\tfor (b = s; *b; b++)\n+\t\t\tfprintf(stderr, \"    %.*s\", (int)(*b)->len, (*b)->buf);\n+\n+\t\tstrbuf_list_free(s);\n+\t\tstrbuf_release(&files);\n+\t\treturn 2;\n+\t}\n+\n+\treturn merge_octopus(bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 7d19d37951..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 2e92019493..28634cf61f 100644\n--- a/git.c\n+++ b/git.c\n@@ -531,6 +531,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400600","messageId":"20200625121953.16991-13-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 12/17] merge-octopus: remove calls to external processes","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:48Z","receivedAt":"2020-06-25T12:49:27Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This removes calls to external processes to avoid reading and writing\nthe index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes(), and is moved from cmd_merge_octopus() to\n   merge_octopus().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_octopus().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-octopus.c | 155 ++++++++++++++++++++++------------------\n 1 file changed, 86 insertions(+), 69 deletions(-)\n\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nindex 6216beaa2b..14310a4eb1 100644\n--- a/builtin/merge-octopus.c\n+++ b/builtin/merge-octopus.c\n@@ -9,33 +9,70 @@\n  */\n \n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"builtin.h\"\n #include \"commit-reach.h\"\n #include \"lockfile.h\"\n-#include \"run-command.h\"\n+#include \"merge-strategies.h\"\n #include \"unpack-trees.h\"\n \n+static int fast_forward(const struct object_id *oids, int nr, int aggressive)\n+{\n+\tint i;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trepo_read_index_preload(the_repository, NULL, 0);\n+\tif (refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL))\n+\t\treturn -1;\n+\n+\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = the_repository->index;\n+\topts.dst_index = the_repository->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct tree *tree;\n+\t\ttree = parse_tree_indirect(oids + i);\n+\t\tif (parse_tree(tree))\n+\t\t\treturn -1;\n+\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n+\t}\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(the_repository->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(the_repository->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n static int write_tree(struct tree **reference_tree)\n {\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tstruct strbuf read_tree = STRBUF_INIT, err = STRBUF_INIT;\n \tstruct object_id oid;\n \tint ret;\n \n-\tcp.git_cmd = 1;\n-\targv_array_push(&cp.args, \"write-tree\");\n-\tret = pipe_command(&cp, NULL, 0, &read_tree, 0, &err, 0);\n-\tif (err.len > 0)\n-\t\tfputs(err.buf, stderr);\n-\n-\tstrbuf_trim_trailing_newline(&read_tree);\n-\tget_oid(read_tree.buf, &oid);\n-\n-\t*reference_tree = lookup_tree(the_repository, &oid);\n-\n-\tstrbuf_release(&read_tree);\n-\tstrbuf_release(&err);\n-\tchild_process_clear(&cp);\n+\tret = write_index_as_tree(&oid, the_repository->index,\n+\t\t\t\t  the_repository->index_file, 0, NULL);\n+\tif (!ret)\n+\t\t*reference_tree = lookup_tree(the_repository, &oid);\n \n \treturn ret;\n }\n@@ -48,12 +85,23 @@ static int merge_octopus(struct commit_list *bases, const char *head_arg,\n \tstruct tree *reference_tree;\n \tstruct commit_list *j;\n \tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n \n \tget_oid(head_arg, &head);\n+\n \treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n \treference_commit[0] = lookup_commit_reference(the_repository, &head);\n \treference_tree = get_commit_tree(reference_commit[0]);\n \n+\tif (repo_index_has_changes(the_repository, reference_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n \tfor (j = remotes; j; j = j->next) {\n \t\tstruct commit *c = j->item;\n \t\tstruct object_id *oid = &c->object.oid;\n@@ -94,43 +142,36 @@ static int merge_octopus(struct commit_list *bases, const char *head_arg,\n \t\t}\n \n \t\tif (!non_ff_merge && can_ff) {\n-\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\n+\t\t\tstruct object_id oids[2];\n \t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n \n-\t\t\tcp.git_cmd = 1;\n-\t\t\targv_array_pushl(&cp.args, \"read-tree\", \"-u\", \"-m\", NULL);\n-\t\t\targv_array_push(&cp.args, oid_to_hex(&head));\n-\t\t\targv_array_push(&cp.args, oid_to_hex(oid));\n+\t\t\toidcpy(oids, &head);\n+\t\t\toidcpy(oids + 1, oid);\n \n-\t\t\tret = run_command(&cp);\n+\t\t\tret = fast_forward(oids, 2, 0);\n \t\t\tif (ret) {\n \t\t\t\tfree(branch_name);\n \t\t\t\tfree_commit_list(common);\n \t\t\t\tgoto out;\n \t\t\t}\n \n-\t\t\tchild_process_clear(&cp);\n \t\t\treferences = 0;\n \t\t\twrite_tree(&reference_tree);\n \t\t} else {\n-\t\t\tstruct commit_list *l;\n+\t\t\tint i = 0;\n \t\t\tstruct tree *next = NULL;\n-\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n \n \t\t\tnon_ff_merge = 1;\n \t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n \n-\t\t\tcp.git_cmd = 1;\n-\t\t\targv_array_pushl(&cp.args, \"read-tree\", \"-u\", \"-m\", \"--aggressive\", NULL);\n+\t\t\tfor (k = common; k; k = k->next)\n+\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n \n-\t\t\tfor (l = common; l; l = l->next)\n-\t\t\t\targv_array_push(&cp.args, oid_to_hex(&l->item->object.oid));\n+\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n+\t\t\toidcpy(oids + (i++), oid);\n \n-\t\t\targv_array_push(&cp.args, oid_to_hex(&reference_tree->object.oid));\n-\t\t\targv_array_push(&cp.args, oid_to_hex(oid));\n-\n-\t\t\tif (run_command(&cp)) {\n+\t\t\tif (fast_forward(oids, i, 1)) {\n \t\t\t\tret = 2;\n \n \t\t\t\tfree(branch_name);\n@@ -139,19 +180,15 @@ static int merge_octopus(struct commit_list *bases, const char *head_arg,\n \t\t\t\tgoto out;\n \t\t\t}\n \n-\t\t\tchild_process_clear(&cp);\n-\n \t\t\tif (write_tree(&next)) {\n-\t\t\t\tstruct child_process cp = CHILD_PROCESS_INIT;\n+\t\t\t\tstruct lock_file lock = LOCK_INIT;\n+\n \t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\t\t\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\t\t\t\tret = !!merge_all(the_repository->index, 0, 0,\n+\t\t\t\t\t\t  merge_one_file_cb, the_repository);\n+\t\t\t\twrite_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n \n-\t\t\t\tcp.git_cmd = 1;\n-\t\t\t\targv_array_pushl(&cp.args, \"merge-index\", \"-o\",\n-\t\t\t\t\t\t \"git-merge-one-file\", \"-a\", NULL);\n-\t\t\t\tif (run_command(&cp))\n-\t\t\t\t\tret = 1;\n-\n-\t\t\t\tchild_process_clear(&cp);\n \t\t\t\twrite_tree(&next);\n \t\t\t}\n \n@@ -178,12 +215,14 @@ int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n \tstruct commit_list *bases = NULL, *remotes = NULL;\n \tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n \tconst char *head_arg = NULL;\n-\tstruct child_process cp = CHILD_PROCESS_INIT;\n-\tstruct strbuf files = STRBUF_INIT;\n \n \tif (argc < 5)\n \t\tusage(builtin_merge_octopus_usage);\n \n+\tsetup_work_tree();\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"corrupted cache\");\n+\n \t/* The first parameters up to -- are merge bases; the rest are\n \t * heads. */\n \tfor (i = 1; i < argc; i++) {\n@@ -215,27 +254,5 @@ int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n \tif (commit_list_count(remotes) < 2)\n \t\treturn 2;\n \n-\tcp.git_cmd = 1;\n-\targv_array_pushl(&cp.args, \"diff-index\", \"--cached\",\n-\t\t\t \"--name-only\", \"HEAD\", \"--\", NULL);\n-\tpipe_command(&cp, NULL, 0, &files, 0, NULL, 0);\n-\tchild_process_clear(&cp);\n-\n-\tif (files.len > 0) {\n-\t\tstruct strbuf **s, **b;\n-\n-\t\ts = strbuf_split(&files, '\\n');\n-\n-\t\tfprintf(stderr, _(\"Error: Your local changes to the following \"\n-\t\t\t\t  \"files would be overwritten by merge\\n\"));\n-\n-\t\tfor (b = s; *b; b++)\n-\t\t\tfprintf(stderr, \"    %.*s\", (int)(*b)->len, (*b)->buf);\n-\n-\t\tstrbuf_list_free(s);\n-\t\tstrbuf_release(&files);\n-\t\treturn 2;\n-\t}\n-\n \treturn merge_octopus(bases, head_arg, remotes);\n }\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400601","messageId":"20200625121953.16991-18-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[RFC PATCH v1 17/17] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-06-25T12:19:53Z","receivedAt":"2020-06-25T12:49:28Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ea8dc58108..f9fa995b4b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1927,6 +1927,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.27.0.139.gc9c318d6bf\n\n"},{"id":"400608","messageId":"CAPx1GvdoVf-yFmbCuFc3ZtPvQ=PjuFrum_mijsQ9u8uf61SOHQ@mail.gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-3-alban.gruin@gmail.com","subject":"Re: [RFC PATCH v1 02/17] merge-one-file: rewrite in C","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2020-06-25T14:55:25Z","receivedAt":"2020-06-25T14:55:38Z","isPatch":true,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"much snippage below, keeping just enough context to see which file,\nfunction, etc:\n\nOn Thu, Jun 25, 2020 at 5:49 AM Alban Gruin <alban.gruin@gmail.com> wrote:\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..4992a6cd30\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,275 @@\n\n> +static int do_merge_one_file(const struct object_id *orig_blob,\n> +                            const struct object_id *our_blob,\n> +                            const struct object_id *their_blob, const char *path,\n> +                            unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +       int ret, source, dest;\n\n> +       source = open(src1.buf, O_RDONLY);\n> +       dest = open(path, O_WRONLY | O_TRUNC);\n> +\n> +       copy_fd(source, dest);\n> +\n> +       close(source);\n> +       close(dest);\n> +\n> +       unlink(orig.buf);\n> +       unlink(src1.buf);\n> +       unlink(src2.buf);\n\nSome of this goes away in subsequent patches, but most of these calls\nshould be checked for error returns, especially the two `open`s in case\nsomeone has messed with permissions.\n\nChris\n"},{"id":"400612","messageId":"80af2da7-d943-94ef-999a-7035bbec0f0d@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-3-alban.gruin@gmail.com","subject":"Re: [RFC PATCH v1 02/17] merge-one-file: rewrite in C","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-06-25T15:16:42Z","receivedAt":"2020-06-25T15:16:48Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nI think this series is a great idea\n\nOn 25/06/2020 13:19, Alban Gruin wrote:\n> This rewrites `git merge-one-file' from shell to C.  This port is very\n> straightforward: it keeps using external processes to edit the index,\n> for instance.  Errors are also displayed with fprintf() instead of\n> error().  Both of these will be addressed in the next few commits,\n> leading to its libification so its main function can be used from other\n> commands directly.\n> \n> This also fixes a bug present in the original script: instead of\n> checking if a _regular_ file exists when a file exists in the branch to\n> merge, but not in our branch, the rewritten version checks if a file of\n> any kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\n> where the branch to merge had a new file, `a/b', but our branch had a\n> directory there; it should have failed because a directory exists, but\n> it did not because there was no regular file called `a/b'.  This test is\n> now marked as successful.\n> \n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>   Makefile                        |   2 +-\n>   builtin.h                       |   1 +\n>   builtin/merge-one-file.c        | 275 ++++++++++++++++++++++++++++++++\n>   git-merge-one-file.sh           | 167 -------------------\n>   git.c                           |   1 +\n>   t/t6035-merge-dir-to-symlink.sh |   2 +-\n>   6 files changed, 279 insertions(+), 169 deletions(-)\n>   create mode 100644 builtin/merge-one-file.c\n>   delete mode 100755 git-merge-one-file.sh\n> \n> diff --git a/Makefile b/Makefile\n> index 372139f1f2..19574f5133 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -596,7 +596,6 @@ SCRIPT_SH += git-bisect.sh\n>   SCRIPT_SH += git-difftool--helper.sh\n>   SCRIPT_SH += git-filter-branch.sh\n>   SCRIPT_SH += git-merge-octopus.sh\n> -SCRIPT_SH += git-merge-one-file.sh\n>   SCRIPT_SH += git-merge-resolve.sh\n>   SCRIPT_SH += git-mergetool.sh\n>   SCRIPT_SH += git-quiltimport.sh\n> @@ -1089,6 +1088,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n>   BUILTIN_OBJS += builtin/merge-base.o\n>   BUILTIN_OBJS += builtin/merge-file.o\n>   BUILTIN_OBJS += builtin/merge-index.o\n> +BUILTIN_OBJS += builtin/merge-one-file.o\n>   BUILTIN_OBJS += builtin/merge-ours.o\n>   BUILTIN_OBJS += builtin/merge-recursive.o\n>   BUILTIN_OBJS += builtin/merge-tree.o\n> diff --git a/builtin.h b/builtin.h\n> index a5ae15bfe5..9205d5ecdc 100644\n> --- a/builtin.h\n> +++ b/builtin.h\n> @@ -172,6 +172,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n>   int cmd_merge_index(int argc, const char **argv, const char *prefix);\n>   int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n>   int cmd_merge_file(int argc, const char **argv, const char *prefix);\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n>   int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n>   int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n>   int cmd_mktag(int argc, const char **argv, const char *prefix);\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..4992a6cd30\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,275 @@\n> +/*\n> + * Builtin \"git merge-one-file\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> + *\n> + * This is the git per-file merge script, called with\n> + *\n> + *   $1 - original file SHA1 (or empty)\n> + *   $2 - file in branch1 SHA1 (or empty)\n> + *   $3 - file in branch2 SHA1 (or empty)\n> + *   $4 - pathname in repository\n> + *   $5 - original file mode (or empty)\n> + *   $6 - file in branch1 mode (or empty)\n> + *   $7 - file in branch2 mode (or empty)\n\nnit pick - these are now argv[1] etc rather than $1 etc\n\n> + *\n> + * Handle some trivial cases.. The _really_ trivial cases have\n> + * been handled already by git read-tree, but that one doesn't\n> + * do any merges that might change the tree layout.\n> + */\n> +\n> +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"commit.h\"\n> +#include \"dir.h\"\n> +#include \"lockfile.h\"\n> +#include \"object-store.h\"\n> +#include \"run-command.h\"\n> +#include \"xdiff-interface.h\"\n> +\n> +static int create_temp_file(const struct object_id *oid, struct strbuf *path)\n> +{\n> +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tint ret;\n> +\n> +\tcp.git_cmd = 1;\n> +\targv_array_pushl(&cp.args, \"unpack-file\", oid_to_hex(oid), NULL);\n> +\tret = pipe_command(&cp, NULL, 0, path, 0, &err, 0);\n> +\tif (!ret && path->len > 0)\n> +\t\tstrbuf_trim_trailing_newline(path);\n> +\n> +\tfprintf(stderr, \"%.*s\", (int) err.len, err.buf);\n> +\tstrbuf_release(&err);\n> +\n> +\treturn ret;\n> +}\n\nI know others will disagree but personally I'm not a huge fan of \nrewriting shell functions in C that forks other builtins and then \nconverting the C to use the internal apis, it seems a much better to \njust write the proper C version the first time. This is especially true \nfor simple function such as the ones in this file. That way the reviewer \ngets a clear view of the final code from the patch, rather than having \nto piece it together from a series of additions and deletions.\n\n> +\n> +static int add_to_index_cacheinfo(unsigned int mode,\n> +\t\t\t\t  const struct object_id *oid, const char *path)\n> +{\n> +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> +\n> +\tcp.git_cmd = 1;\n> +\targv_array_pushl(&cp.args, \"update-index\", \"--add\", \"--cacheinfo\", NULL);\n> +\targv_array_pushf(&cp.args, \"%o,%s,%s\", mode, oid_to_hex(oid), path);\n> +\treturn run_command(&cp);\n> +}\n> +\n> +static int remove_from_index(const char *path)\n> +{\n> +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> +\n> +\tcp.git_cmd = 1;\n> +\targv_array_pushl(&cp.args, \"update-index\", \"--remove\", \"--\", path, NULL);\n> +\treturn run_command(&cp);\n> +}\n> +\n> +static int checkout_from_index(const char *path)\n> +{\n> +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> +\n> +\tcp.git_cmd = 1;\n> +\targv_array_pushl(&cp.args, \"checkout-index\", \"-u\", \"-f\", \"--\", path, NULL);\n> +\treturn run_command(&cp);\n> +}\n> +\n> +static int merge_one_file_deleted(const struct object_id *orig_blob,\n> +\t\t\t\t  const struct object_id *our_blob,\n> +\t\t\t\t  const struct object_id *their_blob, const char *path,\n> +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif ((our_blob && orig_mode != our_mode) ||\n> +\t    (their_blob && orig_mode != their_mode)) {\n> +\t\tfprintf(stderr, \"ERROR: File %s deleted on one branch but had its\\n\", path);\n> +\t\tfprintf(stderr, \"ERROR: permissions changed on the other.\\n\");\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tif (our_blob) {\n> +\t\tprintf(\"Removing %s\\n\", path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\tremove_path(path);\n> +\t}\n> +\n> +\treturn remove_from_index(path);\n> +}\n> +\n> +static int do_merge_one_file(const struct object_id *orig_blob,\n> +\t\t\t     const struct object_id *our_blob,\n> +\t\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tint ret, source, dest;\n> +\tstruct strbuf src1 = STRBUF_INIT, src2 = STRBUF_INIT, orig = STRBUF_INIT;\n> +\tstruct child_process cp_merge = CHILD_PROCESS_INIT,\n> +\t\tcp_checkout = CHILD_PROCESS_INIT,\n> +\t\tcp_update = CHILD_PROCESS_INIT;\n> +\n> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK) {\n> +\t\tfprintf(stderr, \"ERROR: %s: Not merging symbolic link changes.\\n\", path);\n> +\t\treturn 1;\n> +\t} else if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK) {\n> +\t\tfprintf(stderr, \"ERROR: %s: Not merging conflicting submodule changes.\\n\",\n> +\t\t\tpath);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tcreate_temp_file(our_blob, &src1);\n> +\tcreate_temp_file(their_blob, &src2);\n> +\n> +\tif (orig_blob) {\n> +\t\tprintf(\"Auto-merging %s\\n\", path);\n> +\t\tcreate_temp_file(orig_blob, &orig);\n> +\t} else {\n> +\t\tprintf(\"Added %s in both, but differently.\\n\", path);\n> +\t\tcreate_temp_file(the_hash_algo->empty_blob, &orig);\n> +\t}\n> +\n> +\tcp_merge.git_cmd = 1;\n> +\targv_array_pushl(&cp_merge.args, \"merge-file\", src1.buf, orig.buf, src2.buf,\n> +\t\t\t NULL);\n> +\tret = run_command(&cp_merge);\n> +\n> +\tif (ret != 0)\n> +\t\tret = 1;\n> +\n> +\tcp_checkout.git_cmd = 1;\n> +\targv_array_pushl(&cp_checkout.args, \"checkout-index\", \"-f\", \"--stage=2\",\n> +\t\t\t \"--\", path, NULL);\n> +\tif (run_command(&cp_checkout))\n> +\t\treturn 1;\n> +\n> +\tsource = open(src1.buf, O_RDONLY);\n> +\tdest = open(path, O_WRONLY | O_TRUNC);\n> +\n> +\tcopy_fd(source, dest);\n> +\n> +\tclose(source);\n> +\tclose(dest);\n> +\n> +\tunlink(orig.buf);\n> +\tunlink(src1.buf);\n> +\tunlink(src2.buf);\n> +\n> +\tstrbuf_release(&src1);\n> +\tstrbuf_release(&src2);\n> +\tstrbuf_release(&orig);\n\nThe whole business of creating temporary files and forking seems like a \nlot of effort compared to calling ll_merge() which would also mean we \nrespect any merge attributes\n\n> +\n> +\tif (ret) {\n> +\t\tfprintf(stderr, \"ERROR: \");\n> +\n> +\t\tif (!orig_blob) {\n\nI think the original does if (ret || !orig_blob) not &&\n> +\t\t\tfprintf(stderr, \"content conflict\");\n> +\t\t\tif (our_mode != their_mode)\n> +\t\t\t\tfprintf(stderr, \", \");\n\nsentence lego, in any case the message below should be printed \nregardless of content conflicts. We should probably mark all these \nmessages for translation as well.\n\n> +\t\t}\n> +\n> +\t\tif (our_mode != their_mode)\n> +\t\t\tfprintf(stderr, \"permissions conflict: %o->%o,%o\",\n> +\t\t\t\torig_mode, our_mode, their_mode);\n> +\n> +\t\tfprintf(stderr, \" in %s\\n\", path);\n> +\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tcp_update.git_cmd = 1;\n> +\targv_array_pushl(&cp_update.args, \"update-index\", \"--\", path, NULL);\n> +\treturn run_command(&cp_update);\n> +}\n> +\n> +static int merge_one_file(const struct object_id *orig_blob,\n> +\t\t\t  const struct object_id *our_blob,\n> +\t\t\t  const struct object_id *their_blob, const char *path,\n> +\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif (orig_blob &&\n> +\t    ((our_blob && oideq(orig_blob, our_blob)) ||\n> +\t     (their_blob && oideq(orig_blob, their_blob))))\n> +\t\treturn merge_one_file_deleted(orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n\nIt would be nice to preserve the comments from the script as I find they \nhelp a lot in understanding which case each piece of code is handling. \nThe code above appears to be handling deletions but does not appear to \ncheck that one side is actually missing. Shouldn't it be something like\n\nif (orig_blob &&\n     ((!their_blob && (our_blob && oideq(orig_blob, our_blob))) ||\n      (!our_blob && (their_blob && oideq(orig_blob, their_blob))))\n\nMaybe this could do with a test case\n\n> +\telse if (!orig_blob && our_blob && !their_blob) {\n> +\t\treturn add_to_index_cacheinfo(our_mode, our_blob, path);\n> +\t} else if (!orig_blob && !our_blob && their_blob) {\n> +\t\tprintf(\"Adding %s\\n\", path);\n> +\n> +\t\tif (file_exists(path)) {\n> +\t\t\tfprintf(stderr, \"ERROR: untracked %s is overwritten by the merge.\\n\", path);\n> +\t\t\treturn 1;\n> +\t\t}\n> +\n> +\t\tif (add_to_index_cacheinfo(their_mode, their_blob, path))\n> +\t\t\treturn 1;\n> +\t\treturn checkout_from_index(path);\n> +\t} else if (!orig_blob && our_blob && their_blob &&\n> +\t\t   oideq(our_blob, their_blob)) {\n> +\t\tif (our_mode != their_mode) {\n> +\t\t\tfprintf(stderr, \"ERROR: File %s added identically in both branches,\", path);\n> +\t\t\tfprintf(stderr, \"ERROR: but permissions conflict %o->%o.\\n\",\n> +\t\t\t\tour_mode, their_mode);\n> +\t\t\treturn 1;\n> +\t\t}\n> +\n> +\t\tprintf(\"Adding %s\\n\", path);\n> +\n> +\t\tif (add_to_index_cacheinfo(our_mode, our_blob, path))\n> +\t\t\treturn 1;\n> +\t\treturn checkout_from_index(path);\n> +\t} else if (our_blob && their_blob)\n> +\t\treturn do_merge_one_file(orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n> +\telse {\n> +\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n> +\n> +\t\tif (orig_blob)\n> +\t\t\torig_hex = oid_to_hex(orig_blob);\n> +\t\tif (our_blob)\n> +\t\t\tour_hex = oid_to_hex(our_blob);\n> +\t\tif (their_blob)\n> +\t\t\ttheir_hex = oid_to_hex(their_blob);\n> +\n> +\t\tfprintf(stderr, \"ERROR: %s: Not handling case %s -> %s -> %s\\n\",\n> +\t\t\tpath, orig_hex, our_hex, their_hex);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static const char builtin_merge_one_file_usage[] =\n> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n> +\t\"Blob ids and modes should be empty for missing files.\";\n> +\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct object_id orig_blob, our_blob, their_blob,\n> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0;\n> +\n> +\tif (argc != 8)\n> +\t\tusage(builtin_merge_one_file_usage);\n> +\n> +\tif (!get_oid(argv[1], &orig_blob)) {\n> +\t\tp_orig_blob = &orig_blob;\n> +\t\torig_mode = strtol(argv[5], NULL, 8);\n\nIt would probably make sense to check that strtol() succeeds (and the \nmode is sensible), and also that get_oid() fails because argv[1] is \nempty, not because it is invalid.\n\nThanks for working on this\nBest Wishes\n\nPhillip\n\n\n> +\t}\n> +\n> +\tif (!get_oid(argv[2], &our_blob)) {\n> +\t\tp_our_blob = &our_blob;\n> +\t\tour_mode = strtol(argv[6], NULL, 8);\n> +\t}\n> +\n> +\tif (!get_oid(argv[3], &their_blob)) {\n> +\t\tp_their_blob = &their_blob;\n> +\t\ttheir_mode = strtol(argv[7], NULL, 8);\n> +\t}\n> +\n> +\treturn merge_one_file(p_orig_blob, p_our_blob, p_their_blob, argv[4],\n> +\t\t\t      orig_mode, our_mode, their_mode);\n> +}\n> diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\n> deleted file mode 100755\n> index f6d9852d2f..0000000000\n> --- a/git-merge-one-file.sh\n> +++ /dev/null\n> @@ -1,167 +0,0 @@\n> -#!/bin/sh\n> -#\n> -# Copyright (c) Linus Torvalds, 2005\n> -#\n> -# This is the git per-file merge script, called with\n> -#\n> -#   $1 - original file SHA1 (or empty)\n> -#   $2 - file in branch1 SHA1 (or empty)\n> -#   $3 - file in branch2 SHA1 (or empty)\n> -#   $4 - pathname in repository\n> -#   $5 - original file mode (or empty)\n> -#   $6 - file in branch1 mode (or empty)\n> -#   $7 - file in branch2 mode (or empty)\n> -#\n> -# Handle some trivial cases.. The _really_ trivial cases have\n> -# been handled already by git read-tree, but that one doesn't\n> -# do any merges that might change the tree layout.\n> -\n> -USAGE='<orig blob> <our blob> <their blob> <path>'\n> -USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n> -LONG_USAGE=\"usage: git merge-one-file $USAGE\n> -\n> -Blob ids and modes should be empty for missing files.\"\n> -\n> -SUBDIRECTORY_OK=Yes\n> -. git-sh-setup\n> -cd_to_toplevel\n> -require_work_tree\n> -\n> -if test $# != 7\n> -then\n> -\techo \"$LONG_USAGE\"\n> -\texit 1\n> -fi\n> -\n> -case \"${1:-.}${2:-.}${3:-.}\" in\n> -#\n> -# Deleted in both or deleted in one and unchanged in the other\n> -#\n> -\"$1..\" | \"$1.$1\" | \"$1$1.\")\n> -\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n> -\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n> -\tthen\n> -\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n> -\t\techo \"ERROR: permissions changed on the other.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\n> -\tif test -n \"$2\"\n> -\tthen\n> -\t\techo \"Removing $4\"\n> -\telse\n> -\t\t# read-tree checked that index matches HEAD already,\n> -\t\t# so we know we do not have this path tracked.\n> -\t\t# there may be an unrelated working tree file here,\n> -\t\t# which we should just leave unmolested.  Make sure\n> -\t\t# we do not have it in the index, though.\n> -\t\texec git update-index --remove -- \"$4\"\n> -\tfi\n> -\tif test -f \"$4\"\n> -\tthen\n> -\t\trm -f -- \"$4\" &&\n> -\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n> -\tfi &&\n> -\t\texec git update-index --remove -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Added in one.\n> -#\n> -\".$2.\")\n> -\t# the other side did not add and we added so there is nothing\n> -\t# to be done, except making the path merged.\n> -\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n> -\t;;\n> -\"..$3\")\n> -\techo \"Adding $4\"\n> -\tif test -f \"$4\"\n> -\tthen\n> -\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n> -\t\texec git checkout-index -u -f -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Added in both, identically (check for same permissions).\n> -#\n> -\".$3$2\")\n> -\tif test \"$6\" != \"$7\"\n> -\tthen\n> -\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n> -\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\techo \"Adding $4\"\n> -\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n> -\t\texec git checkout-index -u -f -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Modified in both, but differently.\n> -#\n> -\"$1$2$3\" | \".$2$3\")\n> -\n> -\tcase \",$6,$7,\" in\n> -\t*,120000,*)\n> -\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n> -\t\texit 1\n> -\t\t;;\n> -\t*,160000,*)\n> -\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n> -\t\texit 1\n> -\t\t;;\n> -\tesac\n> -\n> -\tsrc1=$(git unpack-file $2)\n> -\tsrc2=$(git unpack-file $3)\n> -\tcase \"$1\" in\n> -\t'')\n> -\t\techo \"Added $4 in both, but differently.\"\n> -\t\torig=$(git unpack-file $(git hash-object /dev/null))\n> -\t\t;;\n> -\t*)\n> -\t\techo \"Auto-merging $4\"\n> -\t\torig=$(git unpack-file $1)\n> -\t\t;;\n> -\tesac\n> -\n> -\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n> -\tret=$?\n> -\tmsg=\n> -\tif test $ret != 0 || test -z \"$1\"\n> -\tthen\n> -\t\tmsg='content conflict'\n> -\t\tret=1\n> -\tfi\n> -\n> -\t# Create the working tree file, using \"our tree\" version from the\n> -\t# index, and then store the result of the merge.\n> -\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n> -\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n> -\n> -\tif test \"$6\" != \"$7\"\n> -\tthen\n> -\t\tif test -n \"$msg\"\n> -\t\tthen\n> -\t\t\tmsg=\"$msg, \"\n> -\t\tfi\n> -\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n> -\t\tret=1\n> -\tfi\n> -\n> -\tif test $ret != 0\n> -\tthen\n> -\t\techo \"ERROR: $msg in $4\" >&2\n> -\t\texit 1\n> -\tfi\n> -\texec git update-index -- \"$4\"\n> -\t;;\n> -\n> -*)\n> -\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n> -\t;;\n> -esac\n> -exit 1\n> diff --git a/git.c b/git.c\n> index a2d337eed7..058d91a2a5 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -532,6 +532,7 @@ static struct cmd_struct commands[] = {\n>   \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n>   \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n>   \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n> +\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>   \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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> diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh\n> index 2eddcc7664..5fb74e39a0 100755\n> --- a/t/t6035-merge-dir-to-symlink.sh\n> +++ b/t/t6035-merge-dir-to-symlink.sh\n> @@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n>   \ttest -h a/b\n>   '\n>   \n> -test_expect_failure 'do not lose untracked in merge (resolve)' '\n> +test_expect_success 'do not lose untracked in merge (resolve)' '\n>   \tgit reset --hard &&\n>   \tgit checkout baseline^0 &&\n>   \t>a/b/c/e &&\n> \n\n"},{"id":"400614","messageId":"6bee3870-a363-2f02-1319-4c0bee7dec95@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-17-alban.gruin@gmail.com","subject":"Re: [RFC PATCH v1 16/17] sequencer: use the \"resolve\" strategy without forking","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-06-25T16:11:24Z","receivedAt":"2020-06-25T16:11:30Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 25/06/2020 13:19, Alban Gruin wrote:\n> This teaches the sequencer to invoke the \"resolve\" strategy with a\n> function call instead of forking.\n\nThis is a good idea, however we should check the existing tests that use \nthis strategy to see if they are doing so to test the \ntry_merge_command() code path. I've got some patches in seen that use \n'--strategy=resolve' to exercise the \"non merge-recursive\" code path, so \nI'll update them to use a proper custom merge strategy.\n\nIs it worth optimizing do_merge() to take advantage of resolve and \noctopus being builtin as well?\n\nBest Wishes\n\nPhil\n\n\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>   sequencer.c | 13 ++++++++++---\n>   1 file changed, 10 insertions(+), 3 deletions(-)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index fd7701c88a..ea8dc58108 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -33,6 +33,7 @@\n>   #include \"commit-reach.h\"\n>   #include \"rebase-interactive.h\"\n>   #include \"reset.h\"\n> +#include \"merge-strategies.h\"\n>   \n>   #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n>   \n> @@ -1922,9 +1923,15 @@ static int do_pick_commit(struct repository *r,\n>   \n>   \t\tcommit_list_insert(base, &common);\n>   \t\tcommit_list_insert(next, &remotes);\n> -\t\tres |= try_merge_command(r, opts->strategy,\n> -\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n> -\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n> +\n> +\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n> +\t\t\trepo_read_index(r);\n> +\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n> +\t\t} else\n> +\t\t\tres |= try_merge_command(r, opts->strategy,\n> +\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n> +\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n> +\n>   \t\tfree_commit_list(common);\n>   \t\tfree_commit_list(remotes);\n>   \t}\n> \n"},{"id":"400625","messageId":"32ce88bd-14ce-0cd4-b938-285a95302cae@gmail.com","threadId":"53755","inReplyTo":"80af2da7-d943-94ef-999a-7035bbec0f0d@gmail.com","subject":"Re: [RFC PATCH v1 02/17] merge-one-file: rewrite in C","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-06-25T18:17:36Z","receivedAt":"2020-06-25T18:17:42Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 25/06/2020 16:16, Phillip Wood wrote:\n> Hi Alban\n> \n> I think this series is a great idea\n> \n> On 25/06/2020 13:19, Alban Gruin wrote:\n>> This rewrites `git merge-one-file' from shell to C.  This port is very\n>> straightforward: it keeps using external processes to edit the index,\n>> for instance.  Errors are also displayed with fprintf() instead of\n>> error().  Both of these will be addressed in the next few commits,\n>> leading to its libification so its main function can be used from other\n>> commands directly.\n>>\n>> This also fixes a bug present in the original script: instead of\n>> checking if a _regular_ file exists when a file exists in the branch to\n>> merge, but not in our branch, the rewritten version checks if a file of\n>> any kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\n>> where the branch to merge had a new file, `a/b', but our branch had a\n>> directory there; it should have failed because a directory exists, but\n>> it did not because there was no regular file called `a/b'.  This test is\n>> now marked as successful.\n>> [...]\n>> +static int merge_one_file(const struct object_id *orig_blob,\n>> +              const struct object_id *our_blob,\n>> +              const struct object_id *their_blob, const char *path,\n>> +              unsigned int orig_mode, unsigned int our_mode, unsigned\n>> int their_mode)\n>> +{\n>> +    if (orig_blob &&\n>> +        ((our_blob && oideq(orig_blob, our_blob)) ||\n>> +         (their_blob && oideq(orig_blob, their_blob))))\n>> +        return merge_one_file_deleted(orig_blob, our_blob,\n>> their_blob, path,\n>> +                          orig_mode, our_mode, their_mode);\n> \n> It would be nice to preserve the comments from the script as I find they\n> help a lot in understanding which case each piece of code is handling.\n> The code above appears to be handling deletions but does not appear to\n> check that one side is actually missing. Shouldn't it be something like\n> \n> if (orig_blob &&\n>     ((!their_blob && (our_blob && oideq(orig_blob, our_blob))) ||\n>      (!our_blob && (their_blob && oideq(orig_blob, their_blob))))\n> \n> Maybe this could do with a test case\n\nThe reason your version works is that if only one side has changed\nread-tree will have done the merge itself so this only gets called if\none side has been deleted. However the original script printed an error\nif someone accidentally called when the content had changed in only one\nside and there were no mode changes. I think we want to keep that behavior.\n\nIn the future we could probably update this to also handle the cases\nthat read-tree normally takes care of rather than erroring out but I\ndon't think it is a high priority.\n\nBest Wishes\n\nPhillip\n"},{"id":"400668","messageId":"0e20fa12-4628-d1fe-fc6e-df83d26edda3@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-7-alban.gruin@gmail.com","subject":"Re: [RFC PATCH v1 06/17] merge-index: libify merge_one_path() and merge_all()","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-06-26T10:13:21Z","receivedAt":"2020-06-26T10:13:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 25/06/2020 13:19, Alban Gruin wrote:\n> The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n> merge-one-file', they delegate the work to another git command, `git\n> merge-index', that will loop over files in the index and call the\n> specified command.  Unfortunately, these functions are not part of\n> libgit.a, which means that once rewritten, the strategies would still\n> have to invoke `merge-one-file' by spawning a new process first.\n> \n> To avoid this, this moves merge_one_path(), merge_all(), and their\n> helpers to merge-strategies.c.  They also take a callback to dictate\n> what they should do for each file.  For now, only one launching a new\n> process is defined to preserve the behaviour of the builtin version.\n> \n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n> \n> Notes:\n>     This patch is best viewed with `--color-moved'.\n> \n>  builtin/merge-index.c | 77 +++------------------------------\n>  merge-strategies.c    | 99 +++++++++++++++++++++++++++++++++++++++++++\n>  merge-strategies.h    | 17 ++++++++\n>  3 files changed, 123 insertions(+), 70 deletions(-)\n> \n> diff --git a/builtin/merge-index.c b/builtin/merge-index.c\n> index 38ea6ad6ca..6cb666cc78 100644\n> --- a/builtin/merge-index.c\n> +++ b/builtin/merge-index.c\n> @@ -1,74 +1,11 @@\n>  #define USE_THE_INDEX_COMPATIBILITY_MACROS\n>  #include \"builtin.h\"\n> -#include \"run-command.h\"\n> -\n> -static const char *pgm;\n> -static int one_shot, quiet;\n> -static int err;\n> -\n> -static int merge_entry(int pos, const char *path)\n> -{\n> -\tint found;\n> -\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n> -\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n> -\tchar ownbuf[4][60];\n> -\n> -\tif (pos >= active_nr)\n> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n> -\tfound = 0;\n> -\tdo {\n> -\t\tconst struct cache_entry *ce = active_cache[pos];\n> -\t\tint stage = ce_stage(ce);\n> -\n> -\t\tif (strcmp(ce->name, path))\n> -\t\t\tbreak;\n> -\t\tfound++;\n> -\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n> -\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n> -\t\targuments[stage] = hexbuf[stage];\n> -\t\targuments[stage + 4] = ownbuf[stage];\n> -\t} while (++pos < active_nr);\n> -\tif (!found)\n> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n> -\n> -\tif (run_command_v_opt(arguments, 0)) {\n> -\t\tif (one_shot)\n> -\t\t\terr++;\n> -\t\telse {\n> -\t\t\tif (!quiet)\n> -\t\t\t\tdie(\"merge program failed\");\n> -\t\t\texit(1);\n> -\t\t}\n> -\t}\n> -\treturn found;\n> -}\n> -\n> -static void merge_one_path(const char *path)\n> -{\n> -\tint pos = cache_name_pos(path, strlen(path));\n> -\n> -\t/*\n> -\t * If it already exists in the cache as stage0, it's\n> -\t * already merged and there is nothing to do.\n> -\t */\n> -\tif (pos < 0)\n> -\t\tmerge_entry(-pos-1, path);\n> -}\n> -\n> -static void merge_all(void)\n> -{\n> -\tint i;\n> -\tfor (i = 0; i < active_nr; i++) {\n> -\t\tconst struct cache_entry *ce = active_cache[i];\n> -\t\tif (!ce_stage(ce))\n> -\t\t\tcontinue;\n> -\t\ti += merge_entry(i, ce->name)-1;\n> -\t}\n> -}\n> +#include \"merge-strategies.h\"\n>  \n>  int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  {\n> -\tint i, force_file = 0;\n> +\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n> +\tconst char *pgm;\n>  \n>  \t/* Without this we cannot rely on waitpid() to tell\n>  \t * what happened to our children.\n> @@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"-a\")) {\n> -\t\t\t\tmerge_all();\n> +\t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n> +\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n>  \t\t}\n> -\t\tmerge_one_path(arg);\n> +\t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n> +\t\t\t\t      merge_program_cb, (void *)pgm);\n>  \t}\n> -\tif (err && !quiet)\n> -\t\tdie(\"merge program failed\");\n>  \treturn err;\n>  }\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index 3a9fce9f22..f4c0b4acd6 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -1,6 +1,7 @@\n>  #include \"cache.h\"\n>  #include \"dir.h\"\n>  #include \"merge-strategies.h\"\n> +#include \"run-command.h\"\n>  #include \"xdiff-interface.h\"\n>  \n>  static int add_to_index_cacheinfo(struct index_state *istate,\n> @@ -189,3 +190,101 @@ int merge_strategies_one_file(struct repository *r,\n>  \n>  \treturn 0;\n>  }\n> +\n> +int merge_program_cb(const struct object_id *orig_blob,\n> +\t\t     const struct object_id *our_blob,\n> +\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t     void *data)\n\nUsing void* is slightly unfortunate but it's needed later.\n\nIt would be nice to check if the program to run is git-merge-one-file\nand call the appropriate function instead in that case so all users of\nmerge-index get the benefit of it being builtin. That probably wants to\nbe done in cmd_merge_index() rather than here though.\n\n> +{\n> +\tchar ownbuf[3][60] = {{0}};\n\nI know this is copied from above but it would be better to use\nGIT_MAX_HEXSZ rather than 60\n\n> +\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n> +\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n> +\t\t\t\t    NULL };\n> +\n> +\tif (orig_blob)\n> +\t\targuments[1] = oid_to_hex(orig_blob);\n> +\tif (our_blob)\n> +\t\targuments[2] = oid_to_hex(our_blob);\n> +\tif (their_blob)\n> +\t\targuments[3] = oid_to_hex(their_blob);\n> +\n> +\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n> +\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n> +\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n\nThese are leaked. Also are you sure we want to fill out the mode if the\ncorresponding blob is missing - I guess it doesn't matter but it would\nbe good to check that - i think the original passed \"\". It also passed\n\"\" rather than \"0000...\" for the blobs that were missing I think.\n\nBest Wishes\n\nPhillip\n\n> +\n> +\treturn run_command_v_opt(arguments, 0);\n> +}\n> +\n> +static int merge_entry(struct index_state *istate, int quiet, int pos,\n> +\t\t       const char *path, merge_cb cb, void *data)\n> +{\n> +\tint found = 0;\n> +\tconst struct object_id *oids[3] = {NULL};\n> +\tunsigned int modes[3] = {0};\n> +\n> +\tdo {\n> +\t\tconst struct cache_entry *ce = istate->cache[pos];\n> +\t\tint stage = ce_stage(ce);\n> +\n> +\t\tif (strcmp(ce->name, path))\n> +\t\t\tbreak;\n> +\t\tfound++;\n> +\t\toids[stage - 1] = &ce->oid;\n> +\t\tmodes[stage - 1] = ce->ce_mode;\n> +\t} while (++pos < istate->cache_nr);\n> +\tif (!found)\n> +\t\treturn error(_(\"%s is not in the cache\"), path);\n> +\n> +\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n> +\t\tif (!quiet)\n> +\t\t\terror(_(\"Merge program failed\"));\n> +\t\treturn -2;\n> +\t}\n> +\n> +\treturn found;\n> +}\n> +\n> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n> +\t\t   const char *path, merge_cb cb, void *data)\n> +{\n> +\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n> +\n> +\t/*\n> +\t * If it already exists in the cache as stage0, it's\n> +\t * already merged and there is nothing to do.\n> +\t */\n> +\tif (pos < 0) {\n> +\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n> +\t\tif (ret == -1)\n> +\t\t\treturn -1;\n> +\t\telse if (ret == -2)\n> +\t\t\treturn 1;\n> +\t}\n> +\treturn 0;\n> +}\n> +\n> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n> +\t      merge_cb cb, void *data)\n> +{\n> +\tint err = 0, i, ret;\n> +\tfor (i = 0; i < istate->cache_nr; i++) {\n> +\t\tconst struct cache_entry *ce = istate->cache[i];\n> +\t\tif (!ce_stage(ce))\n> +\t\t\tcontinue;\n> +\n> +\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n> +\t\tif (ret > 0)\n> +\t\t\ti += ret - 1;\n> +\t\telse if (ret == -1)\n> +\t\t\treturn -1;\n> +\t\telse if (ret == -2) {\n> +\t\t\tif (oneshot)\n> +\t\t\t\terr++;\n> +\t\t\telse\n> +\t\t\t\treturn 1;\n> +\t\t}\n> +\t}\n> +\n> +\treturn err;\n> +}\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> index b527d145c7..cf78d7eaf4 100644\n> --- a/merge-strategies.h\n> +++ b/merge-strategies.h\n> @@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n>  \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>  \t\t\t      unsigned int their_mode);\n>  \n> +typedef int (*merge_cb)(const struct object_id *orig_blob,\n> +\t\t\tconst struct object_id *our_blob,\n> +\t\t\tconst struct object_id *their_blob, const char *path,\n> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\tvoid *data);\n> +\n> +int merge_program_cb(const struct object_id *orig_blob,\n> +\t\t     const struct object_id *our_blob,\n> +\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t     void *data);\n> +\n> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n> +\t\t   const char *path, merge_cb cb, void *data);\n> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n> +\t      merge_cb cb, void *data);\n> +\n>  #endif /* MERGE_STRATEGIES_H */\n> \n\n"},{"id":"400686","messageId":"b7b7915d-6ca3-0a0a-94bd-7298ecaf45d2@gmail.com","threadId":"53755","inReplyTo":"0e20fa12-4628-d1fe-fc6e-df83d26edda3@gmail.com","subject":"Re: [RFC PATCH v1 06/17] merge-index: libify merge_one_path() and merge_all()","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-06-26T14:32:09Z","receivedAt":"2020-06-26T14:32:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 26/06/2020 11:13, Phillip Wood wrote:\n> Hi Alban\n> \n> On 25/06/2020 13:19, Alban Gruin wrote:\n>> The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n>> merge-one-file', they delegate the work to another git command, `git\n>> merge-index', that will loop over files in the index and call the\n>> specified command.  Unfortunately, these functions are not part of\n>> libgit.a, which means that once rewritten, the strategies would still\n>> have to invoke `merge-one-file' by spawning a new process first.\n>>\n>> To avoid this, this moves merge_one_path(), merge_all(), and their\n>> helpers to merge-strategies.c.  They also take a callback to dictate\n>> what they should do for each file.  For now, only one launching a new\n>> process is defined to preserve the behaviour of the builtin version.\n>>\n>> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n>> ---\n>>\n>> Notes:\n>>      This patch is best viewed with `--color-moved'.\n>>\n>>   builtin/merge-index.c | 77 +++------------------------------\n>>   merge-strategies.c    | 99 +++++++++++++++++++++++++++++++++++++++++++\n>>   merge-strategies.h    | 17 ++++++++\n>>   3 files changed, 123 insertions(+), 70 deletions(-)\n>>\n>> diff --git a/builtin/merge-index.c b/builtin/merge-index.c\n>> index 38ea6ad6ca..6cb666cc78 100644\n>> --- a/builtin/merge-index.c\n>> +++ b/builtin/merge-index.c\n>> @@ -1,74 +1,11 @@\n>>   #define USE_THE_INDEX_COMPATIBILITY_MACROS\n>>   #include \"builtin.h\"\n>> -#include \"run-command.h\"\n>> -\n>> -static const char *pgm;\n>> -static int one_shot, quiet;\n>> -static int err;\n>> -\n>> -static int merge_entry(int pos, const char *path)\n>> -{\n>> -\tint found;\n>> -\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n>> -\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n>> -\tchar ownbuf[4][60];\n>> -\n>> -\tif (pos >= active_nr)\n>> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n>> -\tfound = 0;\n>> -\tdo {\n>> -\t\tconst struct cache_entry *ce = active_cache[pos];\n>> -\t\tint stage = ce_stage(ce);\n>> -\n>> -\t\tif (strcmp(ce->name, path))\n>> -\t\t\tbreak;\n>> -\t\tfound++;\n>> -\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n>> -\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n>> -\t\targuments[stage] = hexbuf[stage];\n>> -\t\targuments[stage + 4] = ownbuf[stage];\n>> -\t} while (++pos < active_nr);\n>> -\tif (!found)\n>> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n>> -\n>> -\tif (run_command_v_opt(arguments, 0)) {\n>> -\t\tif (one_shot)\n>> -\t\t\terr++;\n>> -\t\telse {\n>> -\t\t\tif (!quiet)\n>> -\t\t\t\tdie(\"merge program failed\");\n>> -\t\t\texit(1);\n>> -\t\t}\n>> -\t}\n>> -\treturn found;\n>> -}\n>> -\n>> -static void merge_one_path(const char *path)\n>> -{\n>> -\tint pos = cache_name_pos(path, strlen(path));\n>> -\n>> -\t/*\n>> -\t * If it already exists in the cache as stage0, it's\n>> -\t * already merged and there is nothing to do.\n>> -\t */\n>> -\tif (pos < 0)\n>> -\t\tmerge_entry(-pos-1, path);\n>> -}\n>> -\n>> -static void merge_all(void)\n>> -{\n>> -\tint i;\n>> -\tfor (i = 0; i < active_nr; i++) {\n>> -\t\tconst struct cache_entry *ce = active_cache[i];\n>> -\t\tif (!ce_stage(ce))\n>> -\t\t\tcontinue;\n>> -\t\ti += merge_entry(i, ce->name)-1;\n>> -\t}\n>> -}\n>> +#include \"merge-strategies.h\"\n>>   \n>>   int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>>   {\n>> -\tint i, force_file = 0;\n>> +\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n>> +\tconst char *pgm;\n>>   \n>>   \t/* Without this we cannot rely on waitpid() to tell\n>>   \t * what happened to our children.\n>> @@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>>   \t\t\t\tcontinue;\n>>   \t\t\t}\n>>   \t\t\tif (!strcmp(arg, \"-a\")) {\n>> -\t\t\t\tmerge_all();\n>> +\t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n>> +\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n>>   \t\t\t\tcontinue;\n>>   \t\t\t}\n>>   \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n>>   \t\t}\n>> -\t\tmerge_one_path(arg);\n>> +\t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n>> +\t\t\t\t      merge_program_cb, (void *)pgm);\n>>   \t}\n>> -\tif (err && !quiet)\n>> -\t\tdie(\"merge program failed\");\n>>   \treturn err;\n>>   }\n>> diff --git a/merge-strategies.c b/merge-strategies.c\n>> index 3a9fce9f22..f4c0b4acd6 100644\n>> --- a/merge-strategies.c\n>> +++ b/merge-strategies.c\n>> @@ -1,6 +1,7 @@\n>>   #include \"cache.h\"\n>>   #include \"dir.h\"\n>>   #include \"merge-strategies.h\"\n>> +#include \"run-command.h\"\n>>   #include \"xdiff-interface.h\"\n>>   \n>>   static int add_to_index_cacheinfo(struct index_state *istate,\n>> @@ -189,3 +190,101 @@ int merge_strategies_one_file(struct repository *r,\n>>   \n>>   \treturn 0;\n>>   }\n>> +\n>> +int merge_program_cb(const struct object_id *orig_blob,\n>> +\t\t     const struct object_id *our_blob,\n>> +\t\t     const struct object_id *their_blob, const char *path,\n>> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t     void *data)\n> \n> Using void* is slightly unfortunate but it's needed later.\n> \n> It would be nice to check if the program to run is git-merge-one-file\n> and call the appropriate function instead in that case so all users of\n> merge-index get the benefit of it being builtin. That probably wants to\n> be done in cmd_merge_index() rather than here though.\n> \n>> +{\n>> +\tchar ownbuf[3][60] = {{0}};\n> \n> I know this is copied from above but it would be better to use\n> GIT_MAX_HEXSZ rather than 60\n> \n>> +\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n>> +\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n>> +\t\t\t\t    NULL };\n>> +\n>> +\tif (orig_blob)\n>> +\t\targuments[1] = oid_to_hex(orig_blob);\n>> +\tif (our_blob)\n>> +\t\targuments[2] = oid_to_hex(our_blob);\n>> +\tif (their_blob)\n>> +\t\targuments[3] = oid_to_hex(their_blob);\n>> +\n>> +\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n>> +\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n>> +\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n\nSorry ignore all the comments below, they are nonsense\n\nBest Wishes\n\nPhillip\n\n> These are leaked. Also are you sure we want to fill out the mode if the\n> corresponding blob is missing - I guess it doesn't matter but it would\n> be good to check that - i think the original passed \"\". It also passed\n> \"\" rather than \"0000...\" for the blobs that were missing I think.\n> \n> Best Wishes\n> \n> Phillip\n> \n>> +\n>> +\treturn run_command_v_opt(arguments, 0);\n>> +}\n>> +\n>> +static int merge_entry(struct index_state *istate, int quiet, int pos,\n>> +\t\t       const char *path, merge_cb cb, void *data)\n>> +{\n>> +\tint found = 0;\n>> +\tconst struct object_id *oids[3] = {NULL};\n>> +\tunsigned int modes[3] = {0};\n>> +\n>> +\tdo {\n>> +\t\tconst struct cache_entry *ce = istate->cache[pos];\n>> +\t\tint stage = ce_stage(ce);\n>> +\n>> +\t\tif (strcmp(ce->name, path))\n>> +\t\t\tbreak;\n>> +\t\tfound++;\n>> +\t\toids[stage - 1] = &ce->oid;\n>> +\t\tmodes[stage - 1] = ce->ce_mode;\n>> +\t} while (++pos < istate->cache_nr);\n>> +\tif (!found)\n>> +\t\treturn error(_(\"%s is not in the cache\"), path);\n>> +\n>> +\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n>> +\t\tif (!quiet)\n>> +\t\t\terror(_(\"Merge program failed\"));\n>> +\t\treturn -2;\n>> +\t}\n>> +\n>> +\treturn found;\n>> +}\n>> +\n>> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n>> +\t\t   const char *path, merge_cb cb, void *data)\n>> +{\n>> +\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n>> +\n>> +\t/*\n>> +\t * If it already exists in the cache as stage0, it's\n>> +\t * already merged and there is nothing to do.\n>> +\t */\n>> +\tif (pos < 0) {\n>> +\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n>> +\t\tif (ret == -1)\n>> +\t\t\treturn -1;\n>> +\t\telse if (ret == -2)\n>> +\t\t\treturn 1;\n>> +\t}\n>> +\treturn 0;\n>> +}\n>> +\n>> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n>> +\t      merge_cb cb, void *data)\n>> +{\n>> +\tint err = 0, i, ret;\n>> +\tfor (i = 0; i < istate->cache_nr; i++) {\n>> +\t\tconst struct cache_entry *ce = istate->cache[i];\n>> +\t\tif (!ce_stage(ce))\n>> +\t\t\tcontinue;\n>> +\n>> +\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n>> +\t\tif (ret > 0)\n>> +\t\t\ti += ret - 1;\n>> +\t\telse if (ret == -1)\n>> +\t\t\treturn -1;\n>> +\t\telse if (ret == -2) {\n>> +\t\t\tif (oneshot)\n>> +\t\t\t\terr++;\n>> +\t\t\telse\n>> +\t\t\t\treturn 1;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\treturn err;\n>> +}\n>> diff --git a/merge-strategies.h b/merge-strategies.h\n>> index b527d145c7..cf78d7eaf4 100644\n>> --- a/merge-strategies.h\n>> +++ b/merge-strategies.h\n>> @@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n>>   \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>>   \t\t\t      unsigned int their_mode);\n>>   \n>> +typedef int (*merge_cb)(const struct object_id *orig_blob,\n>> +\t\t\tconst struct object_id *our_blob,\n>> +\t\t\tconst struct object_id *their_blob, const char *path,\n>> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t\tvoid *data);\n>> +\n>> +int merge_program_cb(const struct object_id *orig_blob,\n>> +\t\t     const struct object_id *our_blob,\n>> +\t\t     const struct object_id *their_blob, const char *path,\n>> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t     void *data);\n>> +\n>> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n>> +\t\t   const char *path, merge_cb cb, void *data);\n>> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n>> +\t      merge_cb cb, void *data);\n>> +\n>>   #endif /* MERGE_STRATEGIES_H */\n>>\n> \n"},{"id":"400687","messageId":"1e1dfe42-ab62-103a-e213-cfa42f5d9792@gmail.com","threadId":"53755","inReplyTo":"32ce88bd-14ce-0cd4-b938-285a95302cae@gmail.com","subject":"Re: [RFC PATCH v1 02/17] merge-one-file: rewrite in C","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-06-26T14:33:31Z","receivedAt":"2020-06-26T14:33:36Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 25/06/2020 19:17, Phillip Wood wrote:\n> On 25/06/2020 16:16, Phillip Wood wrote:\n>> Hi Alban\n>>\n>> I think this series is a great idea\n>>\n>> On 25/06/2020 13:19, Alban Gruin wrote:\n>>> This rewrites `git merge-one-file' from shell to C.  This port is very\n>>> straightforward: it keeps using external processes to edit the index,\n>>> for instance.  Errors are also displayed with fprintf() instead of\n>>> error().  Both of these will be addressed in the next few commits,\n>>> leading to its libification so its main function can be used from other\n>>> commands directly.\n>>>\n>>> This also fixes a bug present in the original script: instead of\n>>> checking if a _regular_ file exists when a file exists in the branch to\n>>> merge, but not in our branch, the rewritten version checks if a file of\n>>> any kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\n>>> where the branch to merge had a new file, `a/b', but our branch had a\n>>> directory there; it should have failed because a directory exists, but\n>>> it did not because there was no regular file called `a/b'.  This test is\n>>> now marked as successful.\n>>> [...]\n>>> +static int merge_one_file(const struct object_id *orig_blob,\n>>> +              const struct object_id *our_blob,\n>>> +              const struct object_id *their_blob, const char *path,\n>>> +              unsigned int orig_mode, unsigned int our_mode, unsigned\n>>> int their_mode)\n>>> +{\n>>> +    if (orig_blob &&\n>>> +        ((our_blob && oideq(orig_blob, our_blob)) ||\n>>> +         (their_blob && oideq(orig_blob, their_blob))))\n>>> +        return merge_one_file_deleted(orig_blob, our_blob,\n>>> their_blob, path,\n>>> +                          orig_mode, our_mode, their_mode);\n>>\n>> It would be nice to preserve the comments from the script as I find they\n>> help a lot in understanding which case each piece of code is handling.\n>> The code above appears to be handling deletions but does not appear to\n>> check that one side is actually missing. Shouldn't it be something like\n>>\n>> if (orig_blob &&\n>>      ((!their_blob && (our_blob && oideq(orig_blob, our_blob))) ||\n>>       (!our_blob && (their_blob && oideq(orig_blob, their_blob))))\n>>\n>> Maybe this could do with a test case\n> \n> The reason your version works is that if only one side has changed\n> read-tree will have done the merge itself so this only gets called if\n> one side has been deleted. However the original script printed an error\n> if someone accidentally called when the content had changed in only one\n> side and there were no mode changes. I think we want to keep that behavior.\n\nActually I think the original probably handles this case by calling 'git \nmerge-file'\n\nBest Wishes\n\nPhillip\n\n> In the future we could probably update this to also handle the cases\n> that read-tree normally takes care of rather than erroring out but I\n> don't think it is a high priority.\n> \n> Best Wishes\n> \n> Phillip\n> \n"},{"id":"401407","messageId":"alpine.LFD.2.21.2007121311490.17922@andromeda.lan","threadId":"53755","inReplyTo":"80af2da7-d943-94ef-999a-7035bbec0f0d@gmail.com","subject":"Re: [RFC PATCH v1 02/17] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-07-12T11:22:26Z","receivedAt":"2020-07-12T11:22:53Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nPhillip Wood (phillip.wood123@gmail.com) a écrit :\n\n> Hi Alban\n> \n> I think this series is a great idea\n> \n> On 25/06/2020 13:19, Alban Gruin wrote:\n> -%<-\n> > diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> > new file mode 100644\n> > index 0000000000..4992a6cd30\n> > --- /dev/null\n> > +++ b/builtin/merge-one-file.c\n> > @@ -0,0 +1,275 @@\n> > +/*\n> > + * Builtin \"git merge-one-file\"\n> > + *\n> > + * Copyright (c) 2020 Alban Gruin\n> > + *\n> > + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> > + *\n> > + * This is the git per-file merge script, called with\n> > + *\n> > + *   $1 - original file SHA1 (or empty)\n> > + *   $2 - file in branch1 SHA1 (or empty)\n> > + *   $3 - file in branch2 SHA1 (or empty)\n> > + *   $4 - pathname in repository\n> > + *   $5 - original file mode (or empty)\n> > + *   $6 - file in branch1 mode (or empty)\n> > + *   $7 - file in branch2 mode (or empty)\n> \n> nit pick - these are now argv[1] etc rather than $1 etc\n> \n\nI'll change that, and replace \"script\" by \"utility\".\n\n> > + *\n> > + * Handle some trivial cases.. The _really_ trivial cases have\n> > + * been handled already by git read-tree, but that one doesn't\n> > + * do any merges that might change the tree layout.\n> > + */\n> > +\n> > +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n> > +#include \"cache.h\"\n> > +#include \"builtin.h\"\n> > +#include \"commit.h\"\n> > +#include \"dir.h\"\n> > +#include \"lockfile.h\"\n> > +#include \"object-store.h\"\n> > +#include \"run-command.h\"\n> > +#include \"xdiff-interface.h\"\n> > +\n> > +static int create_temp_file(const struct object_id *oid, struct strbuf\n> > *path)\n> > +{\n> > +\tstruct child_process cp = CHILD_PROCESS_INIT;\n> > +\tstruct strbuf err = STRBUF_INIT;\n> > +\tint ret;\n> > +\n> > +\tcp.git_cmd = 1;\n> > +\targv_array_pushl(&cp.args, \"unpack-file\", oid_to_hex(oid), NULL);\n> > +\tret = pipe_command(&cp, NULL, 0, path, 0, &err, 0);\n> > +\tif (!ret && path->len > 0)\n> > +\t\tstrbuf_trim_trailing_newline(path);\n> > +\n> > +\tfprintf(stderr, \"%.*s\", (int) err.len, err.buf);\n> > +\tstrbuf_release(&err);\n> > +\n> > +\treturn ret;\n> > +}\n> \n> I know others will disagree but personally I'm not a huge fan of rewriting\n> shell functions in C that forks other builtins and then converting the C to\n> use the internal apis, it seems a much better to just write the proper C\n> version the first time. This is especially true for simple function such as\n> the ones in this file. That way the reviewer gets a clear view of the final\n> code from the patch, rather than having to piece it together from a series of\n> additions and deletions.\n> \n\nI understand -- I'll squash the \"rewrite\" and \"use internal APIs\" patches \ntogether as a last step for the v2, so I'd be able to get them back with \nall the changes made in the v2 if needed.\n\n> -%<-\n> > +static int do_merge_one_file(const struct object_id *orig_blob,\n> > +\t\t\t     const struct object_id *our_blob,\n> > +\t\t\t     const struct object_id *their_blob, const char\n> > *path,\n> > +\t\t\t     unsigned int orig_mode, unsigned int our_mode,\n> > unsigned int their_mode)\n> > +{\n> > +\tint ret, source, dest;\n> > +\tstruct strbuf src1 = STRBUF_INIT, src2 = STRBUF_INIT, orig =\n> > STRBUF_INIT;\n> > +\tstruct child_process cp_merge = CHILD_PROCESS_INIT,\n> > +\t\tcp_checkout = CHILD_PROCESS_INIT,\n> > +\t\tcp_update = CHILD_PROCESS_INIT;\n> > +\n> > +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK) {\n> > +\t\tfprintf(stderr, \"ERROR: %s: Not merging symbolic link\n> > changes.\\n\", path);\n> > +\t\treturn 1;\n> > +\t} else if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK) {\n> > +\t\tfprintf(stderr, \"ERROR: %s: Not merging conflicting submodule\n> > changes.\\n\",\n> > +\t\t\tpath);\n> > +\t\treturn 1;\n> > +\t}\n> > +\n> > +\tcreate_temp_file(our_blob, &src1);\n> > +\tcreate_temp_file(their_blob, &src2);\n> > +\n> > +\tif (orig_blob) {\n> > +\t\tprintf(\"Auto-merging %s\\n\", path);\n> > +\t\tcreate_temp_file(orig_blob, &orig);\n> > +\t} else {\n> > +\t\tprintf(\"Added %s in both, but differently.\\n\", path);\n> > +\t\tcreate_temp_file(the_hash_algo->empty_blob, &orig);\n> > +\t}\n> > +\n> > +\tcp_merge.git_cmd = 1;\n> > +\targv_array_pushl(&cp_merge.args, \"merge-file\", src1.buf, orig.buf,\n> > src2.buf,\n> > +\t\t\t NULL);\n> > +\tret = run_command(&cp_merge);\n> > +\n> > +\tif (ret != 0)\n> > +\t\tret = 1;\n> > +\n> > +\tcp_checkout.git_cmd = 1;\n> > +\targv_array_pushl(&cp_checkout.args, \"checkout-index\", \"-f\",\n> > \"--stage=2\",\n> > +\t\t\t \"--\", path, NULL);\n> > +\tif (run_command(&cp_checkout))\n> > +\t\treturn 1;\n> > +\n> > +\tsource = open(src1.buf, O_RDONLY);\n> > +\tdest = open(path, O_WRONLY | O_TRUNC);\n> > +\n> > +\tcopy_fd(source, dest);\n> > +\n> > +\tclose(source);\n> > +\tclose(dest);\n> > +\n> > +\tunlink(orig.buf);\n> > +\tunlink(src1.buf);\n> > +\tunlink(src2.buf);\n> > +\n> > +\tstrbuf_release(&src1);\n> > +\tstrbuf_release(&src2);\n> > +\tstrbuf_release(&orig);\n> \n> The whole business of creating temporary files and forking seems like a lot of\n> effort compared to calling ll_merge() which would also mean we respect any\n> merge attributes\n> \n> > +\n> > +\tif (ret) {\n> > +\t\tfprintf(stderr, \"ERROR: \");\n> > +\n> > +\t\tif (!orig_blob) {\n> \n> I think the original does if (ret || !orig_blob) not &&\n\nGood catch.\n\n> > +\t\t\tfprintf(stderr, \"content conflict\");\n> > +\t\t\tif (our_mode != their_mode)\n> > +\t\t\t\tfprintf(stderr, \", \");\n> \n> sentence lego, in any case the message below should be printed regardless of\n> content conflicts. We should probably mark all these messages for translation\n> as well.\n> \n\nYeah, I think I will replace them with two calls to `error()'.\n\n> -%<-\n> > +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> > +{\n> > +\tstruct object_id orig_blob, our_blob, their_blob,\n> > +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> > +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0;\n> > +\n> > +\tif (argc != 8)\n> > +\t\tusage(builtin_merge_one_file_usage);\n> > +\n> > +\tif (!get_oid(argv[1], &orig_blob)) {\n> > +\t\tp_orig_blob = &orig_blob;\n> > +\t\torig_mode = strtol(argv[5], NULL, 8);\n> \n> It would probably make sense to check that strtol() succeeds (and the mode is\n> sensible), and also that get_oid() fails because argv[1] is empty, not because\n> it is invalid.\n> \n\nChecking that `orig_mode' and friends are lower than 0800, and that \n`*argv[1]' is not equal to '\\0' should be enough, right?\n\n> Thanks for working on this\n\nAs always, thank you for your reviews.\n\n> Best Wishes\n> \n> Phillip\n> \n> \n\nCheers,\nAlban\n"},{"id":"401408","messageId":"alpine.LFD.2.21.2007121323220.17922@andromeda.lan","threadId":"53755","inReplyTo":"6bee3870-a363-2f02-1319-4c0bee7dec95@gmail.com","subject":"Re: [RFC PATCH v1 16/17] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-07-12T11:27:03Z","receivedAt":"2020-07-12T11:27:12Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nPhillip Wood (phillip.wood123@gmail.com) a écrit :\n\n> Hi Alban\n> \n> On 25/06/2020 13:19, Alban Gruin wrote:\n> > This teaches the sequencer to invoke the \"resolve\" strategy with a\n> > function call instead of forking.\n> \n> This is a good idea, however we should check the existing tests that use this\n> strategy to see if they are doing so to test the try_merge_command() code\n> path. I've got some patches in seen that use '--strategy=resolve' to exercise\n> the \"non merge-recursive\" code path, so I'll update them to use a proper\n> custom merge strategy.\n> \n> Is it worth optimizing do_merge() to take advantage of resolve and octopus\n> being builtin as well?\n> \n\nHmm, I see that do_merge() doesn't call directly the strategies, and \ndelegates this work to git-merge.  If calling the new APIs does not imply \nto copy/paste too much code from merge.c, then my answer is yes.\n\n> Best Wishes\n> \n> Phil\n> \n\nCheers,\nAlban\n"},{"id":"401409","messageId":"alpine.LFD.2.21.2007121330130.17922@andromeda.lan","threadId":"53755","inReplyTo":"0e20fa12-4628-d1fe-fc6e-df83d26edda3@gmail.com","subject":"Re: [RFC PATCH v1 06/17] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-07-12T11:36:37Z","receivedAt":"2020-07-12T11:36:47Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nPhillip Wood (phillip.wood123@gmail.com) a écrit :\n\n> Hi Alban\n> \n> On 25/06/2020 13:19, Alban Gruin wrote:\n> -%<-\n> > diff --git a/merge-strategies.c b/merge-strategies.c\n> > index 3a9fce9f22..f4c0b4acd6 100644\n> > --- a/merge-strategies.c\n> > +++ b/merge-strategies.c\n> > @@ -1,6 +1,7 @@\n> >  #include \"cache.h\"\n> >  #include \"dir.h\"\n> >  #include \"merge-strategies.h\"\n> > +#include \"run-command.h\"\n> >  #include \"xdiff-interface.h\"\n> >  \n> >  static int add_to_index_cacheinfo(struct index_state *istate,\n> > @@ -189,3 +190,101 @@ int merge_strategies_one_file(struct repository *r,\n> >  \n> >  \treturn 0;\n> >  }\n> > +\n> > +int merge_program_cb(const struct object_id *orig_blob,\n> > +\t\t     const struct object_id *our_blob,\n> > +\t\t     const struct object_id *their_blob, const char *path,\n> > +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> > +\t\t     void *data)\n> \n> Using void* is slightly unfortunate but it's needed later.\n> \n> It would be nice to check if the program to run is git-merge-one-file\n> and call the appropriate function instead in that case so all users of\n> merge-index get the benefit of it being builtin. That probably wants to\n> be done in cmd_merge_index() rather than here though.\n> \n\nDunno, I am not completely comfortable with changing a parameter that \nspecifically describe a program, to a parameter that may be a program, \nexcept in one case where `merge-index' should lock the index, setup the \nworktree, and call a function instead.\n\nWell, I say that, but implementing that behaviour is not that hard:\n\n-- snip --\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 6cb666cc78..19fff9a113 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,11 +1,15 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \tconst char *pgm;\n+\tvoid *data;\n+\tmerge_cb merge_action;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -26,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n+\t\tmerge_action = merge_one_file_cb;\n+\t\tdata = (void *)the_repository;\n+\n+\t\tsetup_work_tree();\n+\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\t} else {\n+\t\tmerge_action = merge_program_cb;\n+\t\tdata = (void *)pgm;\n+\t}\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -36,13 +52,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n-\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n+\t\t\t\t\t\t merge_action, data);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n-\t\t\t\t      merge_program_cb, (void *)pgm);\n+\t\t\t\t      merge_action, data);\n+\t}\n+\n+\tif (merge_action == merge_one_file_cb) {\n+\t\tif (err) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n \t}\n \treturn err;\n }\n-- snap --\n\n> > +{\n> > +\tchar ownbuf[3][60] = {{0}};\n> \n> I know this is copied from above but it would be better to use\n> GIT_MAX_HEXSZ rather than 60\n> \n\nCheers,\nAlban\n\n"},{"id":"401416","messageId":"6f1427f7-29f3-ef77-b2a9-41264dc2fd32@gmail.com","threadId":"53755","inReplyTo":"alpine.LFD.2.21.2007121330130.17922@andromeda.lan","subject":"Re: [RFC PATCH v1 06/17] merge-index: libify merge_one_path() and merge_all()","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-07-12T18:02:32Z","receivedAt":"2020-07-12T18:02:42Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 12/07/2020 12:36, Alban Gruin wrote:\n> Hi Phillip,\n> \n> Phillip Wood (phillip.wood123@gmail.com) a écrit :\n> \n>> Hi Alban\n>>\n>> On 25/06/2020 13:19, Alban Gruin wrote:\n>> -%<-\n>>> diff --git a/merge-strategies.c b/merge-strategies.c\n>>> index 3a9fce9f22..f4c0b4acd6 100644\n>>> --- a/merge-strategies.c\n>>> +++ b/merge-strategies.c\n>>> @@ -1,6 +1,7 @@\n>>>  #include \"cache.h\"\n>>>  #include \"dir.h\"\n>>>  #include \"merge-strategies.h\"\n>>> +#include \"run-command.h\"\n>>>  #include \"xdiff-interface.h\"\n>>>  \n>>>  static int add_to_index_cacheinfo(struct index_state *istate,\n>>> @@ -189,3 +190,101 @@ int merge_strategies_one_file(struct repository *r,\n>>>  \n>>>  \treturn 0;\n>>>  }\n>>> +\n>>> +int merge_program_cb(const struct object_id *orig_blob,\n>>> +\t\t     const struct object_id *our_blob,\n>>> +\t\t     const struct object_id *their_blob, const char *path,\n>>> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>>> +\t\t     void *data)\n>>\n>> Using void* is slightly unfortunate but it's needed later.\n>>\n>> It would be nice to check if the program to run is git-merge-one-file\n>> and call the appropriate function instead in that case so all users of\n>> merge-index get the benefit of it being builtin. That probably wants to\n>> be done in cmd_merge_index() rather than here though.\n>>\n> \n> Dunno, I am not completely comfortable with changing a parameter that \n> specifically describe a program, to a parameter that may be a program, \n> except in one case where `merge-index' should lock the index, setup the \n> worktree, and call a function instead.\n\nThere is some previous discussion about this at\nhttps://lore.kernel.org/git/xmqqblv5kr9u.fsf@gitster-ct.c.googlers.com/\n\nI'll try and have a proper look at your comments towards the end of the\nweek (or maybe the week after the way things are at the moment...)\n\nBest Wishes\n\nPhillip\n\n> Well, I say that, but implementing that behaviour is not that hard:\n> \n> -- snip --\n> diff --git a/builtin/merge-index.c b/builtin/merge-index.c\n> index 6cb666cc78..19fff9a113 100644\n> --- a/builtin/merge-index.c\n> +++ b/builtin/merge-index.c\n> @@ -1,11 +1,15 @@\n>  #define USE_THE_INDEX_COMPATIBILITY_MACROS\n>  #include \"builtin.h\"\n> +#include \"lockfile.h\"\n>  #include \"merge-strategies.h\"\n>  \n>  int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n>  \tconst char *pgm;\n> +\tvoid *data;\n> +\tmerge_cb merge_action;\n> +\tstruct lock_file lock = LOCK_INIT;\n>  \n>  \t/* Without this we cannot rely on waitpid() to tell\n>  \t * what happened to our children.\n> @@ -26,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  \t\tquiet = 1;\n>  \t\ti++;\n>  \t}\n> +\n>  \tpgm = argv[i++];\n> +\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n> +\t\tmerge_action = merge_one_file_cb;\n> +\t\tdata = (void *)the_repository;\n> +\n> +\t\tsetup_work_tree();\n> +\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n> +\t} else {\n> +\t\tmerge_action = merge_program_cb;\n> +\t\tdata = (void *)pgm;\n> +\t}\n> +\n>  \tfor (; i < argc; i++) {\n>  \t\tconst char *arg = argv[i];\n>  \t\tif (!force_file && *arg == '-') {\n> @@ -36,13 +52,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"-a\")) {\n>  \t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n> -\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n> +\t\t\t\t\t\t merge_action, data);\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n>  \t\t}\n>  \t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n> -\t\t\t\t      merge_program_cb, (void *)pgm);\n> +\t\t\t\t      merge_action, data);\n> +\t}\n> +\n> +\tif (merge_action == merge_one_file_cb) {\n> +\t\tif (err) {\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn err;\n> +\t\t}\n> +\n> +\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>  \t}\n>  \treturn err;\n>  }\n> -- snap --\n> \n>>> +{\n>>> +\tchar ownbuf[3][60] = {{0}};\n>>\n>> I know this is copied from above but it would be better to use\n>> GIT_MAX_HEXSZ rather than 60\n>>\n> \n> Cheers,\n> Alban\n> \n\n"},{"id":"401421","messageId":"alpine.LFD.2.21.2007122205480.4475@andromeda.lan","threadId":"53755","inReplyTo":"6f1427f7-29f3-ef77-b2a9-41264dc2fd32@gmail.com","subject":"Re: [RFC PATCH v1 06/17] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-07-12T20:10:54Z","receivedAt":"2020-07-12T20:11:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nPhillip Wood (phillip.wood123@gmail.com) a écrit :\n\n> Hi Alban\n> \n> On 12/07/2020 12:36, Alban Gruin wrote:\n> > Hi Phillip,\n> > \n> > Phillip Wood (phillip.wood123@gmail.com) a écrit :\n> > \n> >> Hi Alban\n> >>\n> >> On 25/06/2020 13:19, Alban Gruin wrote:\n> >> -%<-\n> >>> diff --git a/merge-strategies.c b/merge-strategies.c\n> >>> index 3a9fce9f22..f4c0b4acd6 100644\n> >>> --- a/merge-strategies.c\n> >>> +++ b/merge-strategies.c\n> >>> @@ -1,6 +1,7 @@\n> >>>  #include \"cache.h\"\n> >>>  #include \"dir.h\"\n> >>>  #include \"merge-strategies.h\"\n> >>> +#include \"run-command.h\"\n> >>>  #include \"xdiff-interface.h\"\n> >>>  \n> >>>  static int add_to_index_cacheinfo(struct index_state *istate,\n> >>> @@ -189,3 +190,101 @@ int merge_strategies_one_file(struct repository *r,\n> >>>  \n> >>>  \treturn 0;\n> >>>  }\n> >>> +\n> >>> +int merge_program_cb(const struct object_id *orig_blob,\n> >>> +\t\t     const struct object_id *our_blob,\n> >>> +\t\t     const struct object_id *their_blob, const char *path,\n> >>> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> >>> +\t\t     void *data)\n> >>\n> >> Using void* is slightly unfortunate but it's needed later.\n> >>\n> >> It would be nice to check if the program to run is git-merge-one-file\n> >> and call the appropriate function instead in that case so all users of\n> >> merge-index get the benefit of it being builtin. That probably wants to\n> >> be done in cmd_merge_index() rather than here though.\n> >>\n> > \n> > Dunno, I am not completely comfortable with changing a parameter that \n> > specifically describe a program, to a parameter that may be a program, \n> > except in one case where `merge-index' should lock the index, setup the \n> > worktree, and call a function instead.\n> \n> There is some previous discussion about this at\n> https://lore.kernel.org/git/xmqqblv5kr9u.fsf@gitster-ct.c.googlers.com/\n> \n\nThanks.  If no-one seems really against doing that, I'll include the patch \nbelow in the v2, with an additional note in the man page.\n\n> I'll try and have a proper look at your comments towards the end of the\n> week (or maybe the week after the way things are at the moment...)\n> \n> Best Wishes\n> \n> Phillip\n> \n\nCheers,\nAlban\n\n"},{"id":"404842","messageId":"20200901105705.6059-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 01/11] t6027: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:56:55Z","receivedAt":"2020-09-01T11:01:12Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6027 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6407-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\nindex 4e6c7cb77e..071d3f7343 100755\n--- a/t/t6407-merge-binary.sh\n+++ b/t/t6407-merge-binary.sh\n@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -35,33 +34,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404843","messageId":"20200901105705.6059-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200625121953.16991-1-alban.gruin@gmail.com","subject":"[PATCH v2 00/11] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:56:54Z","receivedAt":"2020-09-01T11:01:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In a effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on it.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without any changes.\n\nThis series is based on d9cd433147 (po: add missing letter for French\nmessage, 2020-08-27).  The tip is tagged as\n\"rewrite-merge-strategies-v2\" at https://github.com/agrn/git.\n\nChanges since v1:\n\n - Merged commits rewriting and libifying scripts.\n\n - Introduce checks in merge-one-file to check that file modes are\n   correct.\n\n - Use ll_merge() instead of xdl_merge().\n\n - merge-index does no longer fork to call git-merge-one-file.\n\n - Remove usage of the_index in merge-one-file.c.\n\n - Mark more strings for translation.\n\n - Carry more comments from the original scripts.\n\n - Use GIT_MAX_HEXSZ instead of hardcoding 60.\n\nAlban Gruin (11):\n  t6027: modernise tests\n  merge-one-file: rewrite in C\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: don't fork if the requested program is\n    `git-merge-one-file'\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Makefile                        |   7 +-\n builtin.h                       |   3 +\n builtin/merge-index.c           | 102 ++----\n builtin/merge-octopus.c         |  65 ++++\n builtin/merge-one-file.c        |  85 +++++\n builtin/merge-recursive.c       |  16 +-\n builtin/merge-resolve.c         |  69 ++++\n builtin/merge.c                 |   9 +-\n cache.h                         |   2 +-\n git-merge-octopus.sh            | 112 ------\n git-merge-one-file.sh           | 167 ---------\n git-merge-resolve.sh            |  54 ---\n git.c                           |   3 +\n merge-strategies.c              | 594 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  44 +++\n merge.c                         |  12 +\n sequencer.c                     |  16 +-\n t/t6407-merge-binary.sh         |  27 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 19 files changed, 942 insertions(+), 447 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v1:\n 1:  50e15b5243 !  1:  28c8fd11b6 t6027: modernise tests\n    @@ Commit message\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n    - ## t/t6027-merge-binary.sh ##\n    -@@ t/t6027-merge-binary.sh: test_description='ask merge-recursive to merge binary files'\n    + ## t/t6407-merge-binary.sh ##\n    +@@ t/t6407-merge-binary.sh: test_description='ask merge-recursive to merge binary files'\n      . ./test-lib.sh\n      \n      test_expect_success setup '\n    @@ t/t6027-merge-binary.sh: test_description='ask merge-recursive to merge binary f\n      \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n      \tgit add m &&\n      \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n    -@@ t/t6027-merge-binary.sh: test_expect_success setup '\n    +@@ t/t6407-merge-binary.sh: test_expect_success setup '\n      '\n      \n      test_expect_success resolve '\n 2:  08a337738e <  -:  ---------- merge-one-file: rewrite in C\n 3:  5da78d5de1 <  -:  ---------- merge-one-file: remove calls to external processes\n 4:  11c0da9e13 <  -:  ---------- merge-one-file: use error() instead of fprintf(stderr, ...)\n 5:  df28965c8e <  -:  ---------- merge-one-file: libify merge_one_file()\n -:  ---------- >  2:  f5ab0fdf0a merge-one-file: rewrite in C\n 6:  84f2f2946a !  3:  7f3ce7da17 merge-index: libify merge_one_path() and merge_all()\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n     \n      ## merge-strategies.c ##\n     @@\n    - #include \"cache.h\"\n      #include \"dir.h\"\n    + #include \"ll-merge.h\"\n      #include \"merge-strategies.h\"\n     +#include \"run-command.h\"\n      #include \"xdiff-interface.h\"\n    @@ merge-strategies.c: int merge_strategies_one_file(struct repository *r,\n     +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n     +\t\t     void *data)\n     +{\n    -+\tchar ownbuf[3][60] = {{0}};\n    ++\tchar ownbuf[3][GIT_MAX_HEXSZ] = {{0}};\n     +\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n     +\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n     +\t\t\t\t    NULL };\n 7:  1f864a4840 <  -:  ---------- merge-resolve: rewrite in C\n 8:  3517990e6a <  -:  ---------- merge-resolve: remove calls to external processes\n 9:  9831fe1729 <  -:  ---------- merge-resolve: libify merge_resolve()\n -:  ---------- >  4:  07e6a6aaef merge-index: don't fork if the requested program is `git-merge-one-file'\n -:  ---------- >  5:  117d4fc840 merge-resolve: rewrite in C\n10:  99d42e8ea1 =  6:  4fc955962b merge-recursive: move better_branch_name() to merge.c\n11:  3182673ea7 <  -:  ---------- merge-octopus: rewrite in C\n12:  8f4cfcefb7 <  -:  ---------- merge-octopus: remove calls to external processes\n13:  d4dba22988 <  -:  ---------- merge-octopus: libify merge_octopus()\n -:  ---------- >  7:  e7b9e15b34 merge-octopus: rewrite in C\n14:  bbe50cd770 =  8:  cd0662201d merge: use the \"resolve\" strategy without forking\n15:  b7aff6fb3a =  9:  0525ff0183 merge: use the \"octopus\" strategy without forking\n16:  c1cdcce3a9 = 10:  6fbf599ba4 sequencer: use the \"resolve\" strategy without forking\n17:  e68765cdc7 = 11:  2c2dc3cc62 sequencer: use the \"octopus\" merge strategy without forking\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404844","messageId":"20200901105705.6059-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 04/11] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:56:58Z","receivedAt":"2020-09-01T11:01:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' has been rewritten and libified, this teaches\n`merge-index' to call merge_strategies_one_file() without forking using\na new callback, merge_one_file_cb().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 29 +++++++++++++++++++++++++++--\n merge-strategies.c    | 11 +++++++++++\n merge-strategies.h    |  6 ++++++\n 3 files changed, 44 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 6cb666cc78..19fff9a113 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,11 +1,15 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \tconst char *pgm;\n+\tvoid *data;\n+\tmerge_cb merge_action;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -26,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n+\t\tmerge_action = merge_one_file_cb;\n+\t\tdata = (void *)the_repository;\n+\n+\t\tsetup_work_tree();\n+\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\t} else {\n+\t\tmerge_action = merge_program_cb;\n+\t\tdata = (void *)pgm;\n+\t}\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -36,13 +52,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n-\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n+\t\t\t\t\t\t merge_action, data);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n-\t\t\t\t      merge_program_cb, (void *)pgm);\n+\t\t\t\t      merge_action, data);\n+\t}\n+\n+\tif (merge_action == merge_one_file_cb) {\n+\t\tif (err) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n \t}\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex ffd6cf77d6..00738863e4 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -199,6 +199,17 @@ int merge_strategies_one_file(struct repository *r,\n \treturn 0;\n }\n \n+int merge_one_file_cb(const struct object_id *orig_blob,\n+\t\t      const struct object_id *our_blob,\n+\t\t      const struct object_id *their_blob, const char *path,\n+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t      void *data)\n+{\n+\treturn merge_strategies_one_file((struct repository *)data,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+}\n+\n int merge_program_cb(const struct object_id *orig_blob,\n \t\t     const struct object_id *our_blob,\n \t\t     const struct object_id *their_blob, const char *path,\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex cf78d7eaf4..40e175ca39 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -16,6 +16,12 @@ typedef int (*merge_cb)(const struct object_id *orig_blob,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_cb(const struct object_id *orig_blob,\n+\t\t      const struct object_id *our_blob,\n+\t\t      const struct object_id *their_blob, const char *path,\n+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t      void *data);\n+\n int merge_program_cb(const struct object_id *orig_blob,\n \t\t     const struct object_id *our_blob,\n \t\t     const struct object_id *their_blob, const char *path,\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404845","messageId":"20200901105705.6059-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 06/11] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:57:00Z","receivedAt":"2020-09-01T11:01:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"get_better_branch_name() will be used by rebase-octopus once it is\nrewritten in C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex 4cad61ffa4..a926b0bc87 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1917,7 +1917,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 5fb88af102..801d673c5f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404848","messageId":"20200901105705.6059-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 02/11] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:56:56Z","receivedAt":"2020-09-01T11:01:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_cache_entry();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_cache();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and ll_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are replaced by calls to\n   cache_file_exists();\n\n - calls to `update-index' are replaced by calls to add_file_to_cache().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   3 +-\n builtin.h                       |   1 +\n builtin/merge-one-file.c        |  85 ++++++++++++++\n git-merge-one-file.sh           | 167 ---------------------------\n git.c                           |   1 +\n merge-strategies.c              | 199 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  13 +++\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 8 files changed, 302 insertions(+), 169 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex 65f8cfb236..8849d54063 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -596,7 +596,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -911,6 +910,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\n@@ -1089,6 +1089,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex a5ae15bfe5..9205d5ecdc 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -172,6 +172,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..306a86c2f0\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,85 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file SHA1 (or empty)\n+ *   argv[2] - file in branch1 SHA1 (or empty)\n+ *   argv[3] - file in branch2 SHA1 (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (!get_oid(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\torig_mode = strtol(argv[5], NULL, 8);\n+\n+\t\tif (!(S_ISREG(orig_mode) || S_ISDIR(orig_mode) || S_ISLNK(orig_mode)))\n+\t\t\tret |= error(_(\"invalid 'orig' mode: %o\"), orig_mode);\n+\t}\n+\n+\tif (!get_oid(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tour_mode = strtol(argv[6], NULL, 8);\n+\n+\t\tif (!(S_ISREG(our_mode) || S_ISDIR(our_mode) || S_ISLNK(our_mode)))\n+\t\t\tret |= error(_(\"invalid 'our' mode: %o\"), our_mode);\n+\t}\n+\n+\tif (!get_oid(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\ttheir_mode = strtol(argv[7], NULL, 8);\n+\n+\t\tif (!(S_ISREG(their_mode) || S_ISDIR(their_mode) || S_ISLNK(their_mode)))\n+\t\t\tret = error(_(\"invalid 'their' mode: %o\"), their_mode);\n+\t}\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_strategies_one_file(the_repository,\n+\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n+\t\t\t\t\torig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn ret;\n+\t}\n+\n+\treturn write_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex 8bd1d7551d..c97fea36c1 100644\n--- a/git.c\n+++ b/git.c\n@@ -534,6 +534,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..f2af4a894d\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,199 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"ll-merge.h\"\n+#include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int add_to_index_cacheinfo(struct index_state *istate,\n+\t\t\t\t  unsigned int mode,\n+\t\t\t\t  const struct object_id *oid, const char *path)\n+{\n+\tstruct cache_entry *ce;\n+\tint len, option;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(0);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n+\tif (add_index_entry(istate, ce, option))\n+\t\treturn error(_(\"%s: cannot add to the index\"), path);\n+\n+\treturn 0;\n+}\n+\n+static int checkout_from_index(struct index_state *istate, const char *path)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\tstruct cache_entry *ce;\n+\n+\tstate.istate = istate;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tce = index_file_exists(istate, path, strlen(path), 0);\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *orig_blob,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\tstruct ll_merge_options merge_opts = {0};\n+\tstruct cache_entry *ce;\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, &null_oid);\n+\t}\n+\n+\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n+\tret = ll_merge(&result, path,\n+\t\t       mmfs + 0, \"orig\",\n+\t\t       mmfs + 1, \"our\",\n+\t\t       mmfs + 2, \"their\",\n+\t\t       istate, &merge_opts);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret > 127 || !orig_blob)\n+\t\tret = error(_(\"content conflict in %s\"), path);\n+\n+\t/* Create the working tree file, using \"our tree\" version from\n+\t   the index, and then store the result of the merge. */\n+\tce = index_file_exists(istate, path, strlen(path), 0);\n+\tif (!ce)\n+\t\tBUG(\"file is not present in the cache?\");\n+\n+\tunlink(path);\n+\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n+\twrite_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (ret && our_mode != their_mode)\n+\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t     orig_mode, our_mode, their_mode, path);\n+\tif (ret)\n+\t\treturn 1;\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_strategies_one_file(struct repository *r,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob, const char *path,\n+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t      unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n+\t\t/* Deleted in both or deleted in one and unchanged in\n+\t\t   the other */\n+\t\treturn merge_one_file_deleted(r->index,\n+\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\telse if (!orig_blob && our_blob && !their_blob) {\n+\t\t/* Added in one.  The other side did not add and we\n+\t\t   added so there is nothing to be done, except making\n+\t\t   the path merged. */\n+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n+\t\t\treturn 1;\n+\t\treturn checkout_from_index(r->index, path);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\t/* Added in both, identically (check for same\n+\t\t   permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n+\t\t\treturn 1;\n+\t\treturn checkout_from_index(r->index, path);\n+\t} else if (our_blob && their_blob)\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(r->index,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\telse {\n+\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n+\n+\t\tif (orig_blob)\n+\t\t\torig_hex = oid_to_hex(orig_blob);\n+\t\tif (our_blob)\n+\t\t\tour_hex = oid_to_hex(our_blob);\n+\t\tif (their_blob)\n+\t\t\ttheir_hex = oid_to_hex(their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..b527d145c7\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,13 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+int merge_strategies_one_file(struct repository *r,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob, const char *path,\n+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t      unsigned int their_mode);\n+\n+#endif /* MERGE_STRATEGIES_H */\ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2eddcc7664..5fb74e39a0 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404849","messageId":"20200901105705.6059-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 10/11] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:57:04Z","receivedAt":"2020-09-01T11:01:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 2425896911..c4c7b28d24 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -33,6 +33,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -1922,9 +1923,15 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404850","messageId":"20200901105705.6059-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 03/11] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:56:57Z","receivedAt":"2020-09-01T11:01:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves merge_one_path(), merge_all(), and their\nhelpers to merge-strategies.c.  They also take a callback to dictate\nwhat they should do for each file.  For now, only one launching a new\nprocess is defined to preserve the behaviour of the builtin version.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 77 +++------------------------------\n merge-strategies.c    | 99 +++++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    | 17 ++++++++\n 3 files changed, 123 insertions(+), 70 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..6cb666cc78 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,11 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n-#include \"run-command.h\"\n-\n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n-\n-static int merge_entry(int pos, const char *path)\n-{\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n-\n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n-\n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n-\t}\n-}\n+#include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tconst char *pgm;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t      merge_program_cb, (void *)pgm);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex f2af4a894d..ffd6cf77d6 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -2,6 +2,7 @@\n #include \"dir.h\"\n #include \"ll-merge.h\"\n #include \"merge-strategies.h\"\n+#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n static int add_to_index_cacheinfo(struct index_state *istate,\n@@ -197,3 +198,101 @@ int merge_strategies_one_file(struct repository *r,\n \n \treturn 0;\n }\n+\n+int merge_program_cb(const struct object_id *orig_blob,\n+\t\t     const struct object_id *our_blob,\n+\t\t     const struct object_id *their_blob, const char *path,\n+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t     void *data)\n+{\n+\tchar ownbuf[3][GIT_MAX_HEXSZ] = {{0}};\n+\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n+\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n+\t\t\t\t    NULL };\n+\n+\tif (orig_blob)\n+\t\targuments[1] = oid_to_hex(orig_blob);\n+\tif (our_blob)\n+\t\targuments[2] = oid_to_hex(our_blob);\n+\tif (their_blob)\n+\t\targuments[3] = oid_to_hex(their_blob);\n+\n+\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n+\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n+\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n+\n+\treturn run_command_v_opt(arguments, 0);\n+}\n+\n+static int merge_entry(struct index_state *istate, int quiet, int pos,\n+\t\t       const char *path, merge_cb cb, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\treturn -2;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t   const char *path, merge_cb cb, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all(struct index_state *istate, int oneshot, int quiet,\n+\t      merge_cb cb, void *data)\n+{\n+\tint err = 0, i, ret;\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2) {\n+\t\t\tif (oneshot)\n+\t\t\t\terr++;\n+\t\t\telse\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex b527d145c7..cf78d7eaf4 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n \t\t\t      unsigned int their_mode);\n \n+typedef int (*merge_cb)(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_program_cb(const struct object_id *orig_blob,\n+\t\t     const struct object_id *our_blob,\n+\t\t     const struct object_id *their_blob, const char *path,\n+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t     void *data);\n+\n+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t   const char *path, merge_cb cb, void *data);\n+int merge_all(struct index_state *istate, int oneshot, int quiet,\n+\t      merge_cb cb, void *data);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404846","messageId":"20200901105705.6059-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 09/11] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:57:03Z","receivedAt":"2020-09-01T11:01:14Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 541d9bed02..90e092ad02 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -744,6 +744,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\"))\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\telse if (!strcmp(strategy, \"octopus\"))\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404847","messageId":"20200901105705.6059-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 11/11] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:57:05Z","receivedAt":"2020-09-01T11:01:14Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex c4c7b28d24..34853b8970 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1927,6 +1927,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404851","messageId":"20200901105705.6059-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 08/11] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:57:02Z","receivedAt":"2020-09-01T11:01:49Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 74829a838e..541d9bed02 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -740,7 +741,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n-\t} else {\n+\t} else if (!strcmp(strategy, \"resolve\"))\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n+\telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n \t\t\t\t\t common, head_arg, remoteheads);\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404852","messageId":"20200901105705.6059-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 07/11] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:57:01Z","receivedAt":"2020-09-01T11:02:04Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes(), and is moved from cmd_merge_octopus() to\n   merge_octopus().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_strategies_octopus().\n\nHere to, merge_strategies_octopus() takes two commit lists and a string\nto reduce frictions when try_merge_strategies() will be modified to call\nit directly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  65 +++++++++++++\n git-merge-octopus.sh    | 112 ----------------------\n git.c                   |   1 +\n merge-strategies.c      | 200 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 271 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 929c3dc3eb..2fb26d9692 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -595,7 +595,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1088,6 +1087,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 6ea207c9fd..5a587ab70c 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -170,6 +170,7 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..37bbdf11cc\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,65 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"corrupted cache\");\n+\n+\t/* The first parameters up to -- are merge bases; the rest are\n+\t * heads. */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tget_oid(argv[i], &oid);\n+\n+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\t\tif (sep_seen)\n+\t\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\t\telse\n+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Reject if this is not an octopus -- resolve should be used\n+\t * instead. */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 7d19d37951..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 794ca6e9f0..df0bebdafc 100644\n--- a/git.c\n+++ b/git.c\n@@ -533,6 +533,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 6b905dfc38..dee86389e3 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"ll-merge.h\"\n #include \"lockfile.h\"\n@@ -392,3 +393,202 @@ int merge_strategies_resolve(struct repository *r,\n \trollback_lock_file(&lock);\n \treturn 2;\n }\n+\n+static int fast_forward(struct repository *r, const struct object_id *oids,\n+\t\t\tint nr, int aggressive)\n+{\n+\tint i;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trepo_read_index_preload(r, NULL, 0);\n+\tif (refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL))\n+\t\treturn -1;\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct tree *tree;\n+\t\ttree = parse_tree_indirect(oids + i);\n+\t\tif (parse_tree(tree))\n+\t\t\treturn -1;\n+\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n+\t}\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL);\n+\tif (!ret)\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint non_ff_merge = 0, ret = 0, references = 1;\n+\tstruct commit **reference_commit;\n+\tstruct tree *reference_tree;\n+\tstruct commit_list *j;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n+\treference_commit[0] = lookup_commit_reference(r, &head);\n+\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n+\n+\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n+\tfor (j = remotes; j && j->item; j = j->next) {\n+\t\tstruct commit *c = j->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct commit_list *common, *k;\n+\t\tchar *branch_name;\n+\t\tint can_ff = 1;\n+\n+\t\tif (ret) {\n+\t\t\t/* We allow only last one to have a\n+\t\t\t   hand-resolvable conflicts.  Last round failed\n+\t\t\t   and we still had a head to merge. */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common)\n+\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n+\n+\t\tif (k) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!non_ff_merge) {\n+\t\t\tint i;\n+\n+\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n+\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n+\t\t\t\t\t       &reference_commit[i]->object.oid);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!non_ff_merge && can_ff) {\n+\t\t\t/* The first head being merged was a\n+\t\t\t   fast-forward.  Advance the reference commit\n+\t\t\t   to the head being merged, and use that tree\n+\t\t\t   as the intermediate result of the merge.  We\n+\t\t\t   still need to count this as part of the\n+\t\t\t   parent set. */\n+\t\t\tstruct object_id oids[2];\n+\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\t\t\toidcpy(oids, &head);\n+\t\t\toidcpy(oids + 1, oid);\n+\n+\t\t\tret = fast_forward(r, oids, 2, 0);\n+\t\t\tif (ret) {\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\treferences = 0;\n+\t\t\twrite_tree(r, &reference_tree);\n+\t\t} else {\n+\t\t\tint i = 0;\n+\t\t\tstruct tree *next = NULL;\n+\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n+\n+\t\t\tnon_ff_merge = 1;\n+\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\t\t\tfor (k = common; k; k = k->next)\n+\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n+\n+\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n+\t\t\toidcpy(oids + (i++), oid);\n+\n+\t\t\tif (fast_forward(r, oids, i, 1)) {\n+\t\t\t\tret = 2;\n+\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tif (write_tree(r, &next)) {\n+\t\t\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\t\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\t\t\tret = !!merge_all(r->index, 0, 0, merge_one_file_cb, r);\n+\t\t\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\t\t\twrite_tree(r, &next);\n+\t\t\t}\n+\n+\t\t\treference_tree = next;\n+\t\t}\n+\n+\t\treference_commit[references++] = c;\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\t}\n+\n+out:\n+\tfree(reference_commit);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 778f8ce9d6..938411a04e 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -37,5 +37,8 @@ int merge_all(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404853","messageId":"20200901105705.6059-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v2 05/11] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-01T10:56:59Z","receivedAt":"2020-09-01T11:02:16Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all() function.  A callback\n   function, merge_one_file_cb(), is added to allow it to call\n   merge_one_file() without forking.\n\nHere too, the index is read in cmd_merge_resolve(), but\nmerge_strategies_resolve() takes care of writing it back to the disk.\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |  2 +-\n builtin.h               |  1 +\n builtin/merge-resolve.c | 69 +++++++++++++++++++++++++++++++++\n git-merge-resolve.sh    | 54 --------------------------\n git.c                   |  1 +\n merge-strategies.c      | 85 +++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 7 files changed, 162 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex 8849d54063..929c3dc3eb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -596,7 +596,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1092,6 +1091,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 9205d5ecdc..6ea207c9fd 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -174,6 +174,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..59f734473b\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,69 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, is_baseless = 1, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/* The first parameters up to -- are merge bases; the rest are\n+\t * heads. */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse if (remote) {\n+\t\t\t/* Give up if we are given two or more remotes.\n+\t\t\t * Not handling octopus. */\n+\t\t\treturn 2;\n+\t\t} else {\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tget_oid(argv[i], &oid);\n+\t\t\tis_baseless &= sep_seen;\n+\n+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\t\tif (sep_seen)\n+\t\t\t\t\tcommit_list_append(commit, &remote);\n+\t\t\t\telse\n+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (is_baseless)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 343fe7bccd..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex c97fea36c1..794ca6e9f0 100644\n--- a/git.c\n+++ b/git.c\n@@ -538,6 +538,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 00738863e4..6b905dfc38 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,8 +1,11 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n #include \"ll-merge.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int add_to_index_cacheinfo(struct index_state *istate,\n@@ -307,3 +310,85 @@ int merge_all(struct index_state *istate, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int add_tree(const struct object_id *oid, struct tree_desc *t)\n+{\n+\tstruct tree *tree;\n+\n+\ttree = parse_tree_indirect(oid);\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tint i = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct object_id head, oid;\n+\tstruct commit_list *j;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\trefresh_index(r->index, 0, NULL, NULL, NULL);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.update = 1;\n+\topts.merge = 1;\n+\topts.aggressive = 1;\n+\n+\tfor (j = bases; j && j->item; j = j->next) {\n+\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n+\t\t\tgoto out;\n+\t}\n+\n+\tif (head_arg && add_tree(&head, t + (i++)))\n+\t\tgoto out;\n+\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n+\t\tgoto out;\n+\n+\tif (i == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (i == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (i >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = i - 1;\n+\t}\n+\n+\tif (unpack_trees(i, t, &opts))\n+\t\tgoto out;\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all(r->index, 0, 0, merge_one_file_cb, r);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+\n+ out:\n+\trollback_lock_file(&lock);\n+\treturn 2;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 40e175ca39..778f8ce9d6 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_strategies_one_file(struct repository *r,\n@@ -33,4 +34,8 @@ int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all(struct index_state *istate, int oneshot, int quiet,\n \t      merge_cb cb, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.28.0.370.g2c2dc3cc62\n\n"},{"id":"404882","messageId":"xmqqk0xdcim6.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20200901105705.6059-3-alban.gruin@gmail.com","subject":"Re: [PATCH v2 02/11] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-09-01T21:06:09Z","receivedAt":"2020-09-01T21:06:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..306a86c2f0\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,85 @@\n> +/*\n> + * Builtin \"git merge-one-file\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> + *\n> + * This is the git per-file merge utility, called with\n> + *\n> + *   argv[1] - original file SHA1 (or empty)\n> + *   argv[2] - file in branch1 SHA1 (or empty)\n> + *   argv[3] - file in branch2 SHA1 (or empty)\n> + *   argv[4] - pathname in repository\n> + *   argv[5] - original file mode (or empty)\n> + *   argv[6] - file in branch1 mode (or empty)\n> + *   argv[7] - file in branch2 mode (or empty)\n> + *\n> + * Handle some trivial cases. The _really_ trivial cases have been\n> + * handled already by git read-tree, but that one doesn't do any merges\n> + * that might change the tree layout.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"lockfile.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_one_file_usage[] =\n> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n> +\t\"Blob ids and modes should be empty for missing files.\";\n> +\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct object_id orig_blob, our_blob, their_blob,\n> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\n> +\tif (argc != 8)\n> +\t\tusage(builtin_merge_one_file_usage);\n> +\n> +\tif (repo_read_index(the_repository) < 0)\n> +\t\tdie(\"invalid index\");\n> +\n> +\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n\nI do understand why we would want merge_strategies_one_file() helper\nintroduced by this step so that the helper can work in an arbitrary\nrepository (hence taking a pointer to repository structure as one of\nits parameters).\n\nBut the \"merge-one-file\" command will always work in the_repository.\nI do not see a point in using helpers that can work in an arbitrary\nrepository, like repo_read_index() or repo_hold_locked_index(), in\nthe above.  I only see downsides --- it is longer to read, makes\nreaders wonder if there is something tricky involving another\nrepository going on, etc.\n\n> +\tif (!get_oid(argv[1], &orig_blob)) {\n> +\t\tp_orig_blob = &orig_blob;\n> +\t\torig_mode = strtol(argv[5], NULL, 8);\n\nWrite a wrapper around strtol(...,...,8) to reduce repetition, and\nmake sure you do not pass NULL as the second parameter to strtol()\nto always check you parsed the string to the end.\n\n> +\tret = merge_strategies_one_file(the_repository,\n> +\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n> +\t\t\t\t\torig_mode, our_mode, their_mode);\n\nHere, as I said above, it is perfectly fine to pass\nthe_repository().\n\n> +\tif (ret) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn ret;\n> +\t}\n> +\n> +\treturn write_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n\nLikewise, I do not see much point in saying the_repository->index; the_index\nis a perfectly fine short-hand.\n\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> new file mode 100644\n> index 0000000000..f2af4a894d\n> --- /dev/null\n> +++ b/merge-strategies.c\n> @@ -0,0 +1,199 @@\n> +#include \"cache.h\"\n> +#include \"dir.h\"\n> +#include \"ll-merge.h\"\n> +#include \"merge-strategies.h\"\n> +#include \"xdiff-interface.h\"\n> +\n> +static int add_to_index_cacheinfo(struct index_state *istate,\n> +\t\t\t\t  unsigned int mode,\n> +\t\t\t\t  const struct object_id *oid, const char *path)\n> +{\n> +\tstruct cache_entry *ce;\n> +\tint len, option;\n> +\n> +\tif (!verify_path(path, mode))\n> +\t\treturn error(_(\"Invalid path '%s'\"), path);\n> +\n> +\tlen = strlen(path);\n> +\tce = make_empty_cache_entry(istate, len);\n> +\n> +\toidcpy(&ce->oid, oid);\n> +\tmemcpy(ce->name, path, len);\n> +\tce->ce_flags = create_ce_flags(0);\n> +\tce->ce_namelen = len;\n> +\tce->ce_mode = create_ce_mode(mode);\n> +\tif (assume_unchanged)\n> +\t\tce->ce_flags |= CE_VALID;\n> +\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n> +\tif (add_index_entry(istate, ce, option))\n> +\t\treturn error(_(\"%s: cannot add to the index\"), path);\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int checkout_from_index(struct index_state *istate, const char *path)\n> +{\n> +\tstruct checkout state = CHECKOUT_INIT;\n> +\tstruct cache_entry *ce;\n> +\n> +\tstate.istate = istate;\n> +\tstate.force = 1;\n> +\tstate.base_dir = \"\";\n> +\tstate.base_dir_len = 0;\n> +\n> +\tce = index_file_exists(istate, path, strlen(path), 0);\n> +\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n> +\t\treturn error(_(\"%s: cannot checkout file\"), path);\n> +\treturn 0;\n> +}\n> +\n> +static int merge_one_file_deleted(struct index_state *istate,\n> +\t\t\t\t  const struct object_id *orig_blob,\n> +\t\t\t\t  const struct object_id *our_blob,\n> +\t\t\t\t  const struct object_id *their_blob, const char *path,\n> +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif ((our_blob && orig_mode != our_mode) ||\n> +\t    (their_blob && orig_mode != their_mode))\n> +\t\treturn error(_(\"File %s deleted on one branch but had its \"\n> +\t\t\t       \"permissions changed on the other.\"), path);\n> +\n> +\tif (our_blob) {\n> +\t\tprintf(_(\"Removing %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\tremove_path(path);\n> +\t}\n> +\n> +\tif (remove_file_from_index(istate, path))\n> +\t\treturn error(\"%s: cannot remove from the index\", path);\n> +\treturn 0;\n> +}\n\nThese functions we see above all are now easy to write these days,\nthanks to the previous work that built many helpers to perform ommon\noperations (e.g. remove_path()).  Reusing them is very good.\n\n> +static int do_merge_one_file(struct index_state *istate,\n> +\t\t\t     const struct object_id *orig_blob,\n> +\t\t\t     const struct object_id *our_blob,\n> +\t\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tint ret, i, dest;\n> +\tmmbuffer_t result = {NULL, 0};\n> +\tmmfile_t mmfs[3];\n> +\tstruct ll_merge_options merge_opts = {0};\n> +\tstruct cache_entry *ce;\n> +\n> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n> +\n> +\tread_mmblob(mmfs + 1, our_blob);\n> +\tread_mmblob(mmfs + 2, their_blob);\n> +\n> +\tif (orig_blob) {\n> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, orig_blob);\n> +\t} else {\n> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, &null_oid);\n> +\t}\n> +\n> +\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n> +\tret = ll_merge(&result, path,\n> +\t\t       mmfs + 0, \"orig\",\n> +\t\t       mmfs + 1, \"our\",\n> +\t\t       mmfs + 2, \"their\",\n> +\t\t       istate, &merge_opts);\n> +\n> +\tfor (i = 0; i < 3; i++)\n> +\t\tfree(mmfs[i].ptr);\n> +\n> +\tif (ret > 127 || !orig_blob)\n> +\t\tret = error(_(\"content conflict in %s\"), path);\n\nThe original only checked if ret is zero or non-zero; here we\nrequire ret to be large.  Intended?  \n\nll_merge() that called ll_xdl_merge() (i.e. the most common case)\nwould return the value returned from xdl_merge(), which can be -1\nwhen we got an error before calling xdl_do_merge().  xdl_do_merge()\nin turn can return -1.  The most common case returns the value\nreturned from xdl_cleanup_merge(), which is 0 for clean merge, and\nany positive integer (not clipped to 127 or 128) for conflicted one.\n\n> +\t/* Create the working tree file, using \"our tree\" version from\n> +\t   the index, and then store the result of the merge. */\n\nStyle. (cf. Documentation/CodingGuidelines).\n\n> +\tce = index_file_exists(istate, path, strlen(path), 0);\n> +\tif (!ce)\n> +\t\tBUG(\"file is not present in the cache?\");\n> +\n> +\tunlink(path);\n> +\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n> +\twrite_in_full(dest, result.ptr, result.size);\n\nIf open() fails, we write to a bogus file descriptor here.\n\n> +\tclose(dest);\n> +\n> +\tfree(result.ptr);\n> +\n> +\tif (ret && our_mode != their_mode)\n> +\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n> +\t\t\t     orig_mode, our_mode, their_mode, path);\n> +\tif (ret)\n> +\t\treturn 1;\n\nWhat is the error returning convention around here?  Our usual\nconvention is that 0 signals a success, and negative reports an\nerror.  Returning the value returned from add_file_to_index() below,\nand error() above, are consistent with the convention, but this one\nreturns 1 that is not.  When deviating from convention, it needs to\nbe documented for the callers in a comment before the function\ndefinition.\n\n> +\n> +\treturn add_file_to_index(istate, path, 0);\n> +}\n\n\n\n> +int merge_strategies_one_file(struct repository *r,\n> +\t\t\t      const struct object_id *orig_blob,\n> +\t\t\t      const struct object_id *our_blob,\n> +\t\t\t      const struct object_id *their_blob, const char *path,\n> +\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n> +\t\t\t      unsigned int their_mode)\n> +{\n> +\tif (orig_blob &&\n> +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n> +\t\t/* Deleted in both or deleted in one and unchanged in\n> +\t\t   the other */\n> +\t\treturn merge_one_file_deleted(r->index,\n> +\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n> +\telse if (!orig_blob && our_blob && !their_blob) {\n> +\t\t/* Added in one.  The other side did not add and we\n> +\t\t   added so there is nothing to be done, except making\n> +\t\t   the path merged. */\n> +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n> +\t} else if (!orig_blob && !our_blob && their_blob) {\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n> +\n> +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n> +\t\t\treturn 1;\n> +\t\treturn checkout_from_index(r->index, path);\n> +\t} else if (!orig_blob && our_blob && their_blob &&\n> +\t\t   oideq(our_blob, their_blob)) {\n> +\t\t/* Added in both, identically (check for same\n> +\t\t   permissions). */\n> +\t\tif (our_mode != their_mode)\n> +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n> +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n> +\t\t\t\t     path, our_mode, their_mode);\n> +\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n> +\t\t\treturn 1;\n> +\t\treturn checkout_from_index(r->index, path);\n> +\t} else if (our_blob && their_blob)\n> +\t\t/* Modified in both, but differently. */\n> +\t\treturn do_merge_one_file(r->index,\n> +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n> +\telse {\n> +\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n> +\n> +\t\tif (orig_blob)\n> +\t\t\torig_hex = oid_to_hex(orig_blob);\n> +\t\tif (our_blob)\n> +\t\t\tour_hex = oid_to_hex(our_blob);\n> +\t\tif (their_blob)\n> +\t\t\ttheir_hex = oid_to_hex(their_blob);\n\nPrepare three char [] buffers and use oid_to_hex_r() instead,\ninstead of relying that we'd have sufficient number of entries in\nthe rotating buffer.\n\n> +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n> +\t\t\t     path, orig_hex, our_hex, their_hex);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> new file mode 100644\n> index 0000000000..b527d145c7\n> --- /dev/null\n> +++ b/merge-strategies.h\n> @@ -0,0 +1,13 @@\n> +#ifndef MERGE_STRATEGIES_H\n> +#define MERGE_STRATEGIES_H\n> +\n> +#include \"object.h\"\n> +\n> +int merge_strategies_one_file(struct repository *r,\n> +\t\t\t      const struct object_id *orig_blob,\n> +\t\t\t      const struct object_id *our_blob,\n> +\t\t\t      const struct object_id *their_blob, const char *path,\n> +\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n> +\t\t\t      unsigned int their_mode);\n> +\n> +#endif /* MERGE_STRATEGIES_H */\n> diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\n> index 2eddcc7664..5fb74e39a0 100755\n> --- a/t/t6415-merge-dir-to-symlink.sh\n> +++ b/t/t6415-merge-dir-to-symlink.sh\n> @@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n>  \ttest -h a/b\n>  '\n>  \n> -test_expect_failure 'do not lose untracked in merge (resolve)' '\n> +test_expect_success 'do not lose untracked in merge (resolve)' '\n>  \tgit reset --hard &&\n>  \tgit checkout baseline^0 &&\n>  \t>a/b/c/e &&\n"},{"id":"404883","messageId":"xmqqft81cidt.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20200901105705.6059-4-alban.gruin@gmail.com","subject":"Re: [PATCH v2 03/11] merge-index: libify merge_one_path() and merge_all()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-09-01T21:11:10Z","receivedAt":"2020-09-01T21:11:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n> merge-one-file', they delegate the work to another git command, `git\n> merge-index', that will loop over files in the index and call the\n> specified command.  Unfortunately, these functions are not part of\n> libgit.a, which means that once rewritten, the strategies would still\n> have to invoke `merge-one-file' by spawning a new process first.\n>\n> To avoid this, this moves merge_one_path(), merge_all(), and their\n> helpers to merge-strategies.c.  They also take a callback to dictate\n> what they should do for each file.  For now, only one launching a new\n> process is defined to preserve the behaviour of the builtin version.\n\n... of the \"builtin\" version?  I thought this series is introducing\na new builtin version?  Puzzled...\n"},{"id":"404916","messageId":"bb2cc3e0-b84e-7814-9898-c6f697f2a72b@gmail.com","threadId":"53755","inReplyTo":"xmqqk0xdcim6.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 02/11] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-02T14:50:57Z","receivedAt":"2020-09-02T14:51:54Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nLe 01/09/2020 à 23:06, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n>> new file mode 100644\n>> index 0000000000..306a86c2f0\n>> --- /dev/null\n>> +++ b/builtin/merge-one-file.c\n>> @@ -0,0 +1,85 @@\n>> +/*\n>> + * Builtin \"git merge-one-file\"\n>> + *\n>> + * Copyright (c) 2020 Alban Gruin\n>> + *\n>> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n>> + *\n>> + * This is the git per-file merge utility, called with\n>> + *\n>> + *   argv[1] - original file SHA1 (or empty)\n>> + *   argv[2] - file in branch1 SHA1 (or empty)\n>> + *   argv[3] - file in branch2 SHA1 (or empty)\n>> + *   argv[4] - pathname in repository\n>> + *   argv[5] - original file mode (or empty)\n>> + *   argv[6] - file in branch1 mode (or empty)\n>> + *   argv[7] - file in branch2 mode (or empty)\n>> + *\n>> + * Handle some trivial cases. The _really_ trivial cases have been\n>> + * handled already by git read-tree, but that one doesn't do any merges\n>> + * that might change the tree layout.\n>> + */\n>> +\n>> +#include \"cache.h\"\n>> +#include \"builtin.h\"\n>> +#include \"lockfile.h\"\n>> +#include \"merge-strategies.h\"\n>> +\n>> +static const char builtin_merge_one_file_usage[] =\n>> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n>> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n>> +\t\"Blob ids and modes should be empty for missing files.\";\n>> +\n>> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n>> +{\n>> +\tstruct object_id orig_blob, our_blob, their_blob,\n>> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n>> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n>> +\tstruct lock_file lock = LOCK_INIT;\n>> +\n>> +\tif (argc != 8)\n>> +\t\tusage(builtin_merge_one_file_usage);\n>> +\n>> +\tif (repo_read_index(the_repository) < 0)\n>> +\t\tdie(\"invalid index\");\n>> +\n>> +\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n> \n> I do understand why we would want merge_strategies_one_file() helper\n> introduced by this step so that the helper can work in an arbitrary\n> repository (hence taking a pointer to repository structure as one of\n> its parameters).\n> \n> But the \"merge-one-file\" command will always work in the_repository.\n> I do not see a point in using helpers that can work in an arbitrary\n> repository, like repo_read_index() or repo_hold_locked_index(), in\n> the above.  I only see downsides --- it is longer to read, makes\n> readers wonder if there is something tricky involving another\n> repository going on, etc.\n> \n\nI was under the impression that using the_index is just deprecated, and\nthat we ought to avoid using it, even in builtins.\n\nWill update that.\n\n>> +\tif (!get_oid(argv[1], &orig_blob)) {\n>> +\t\tp_orig_blob = &orig_blob;\n>> +\t\torig_mode = strtol(argv[5], NULL, 8);\n> \n> Write a wrapper around strtol(...,...,8) to reduce repetition, and\n> make sure you do not pass NULL as the second parameter to strtol()\n> to always check you parsed the string to the end.\n> \n>> +\tret = merge_strategies_one_file(the_repository,\n>> +\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n>> +\t\t\t\t\torig_mode, our_mode, their_mode);\n> \n> Here, as I said above, it is perfectly fine to pass\n> the_repository().\n> \n>> +\tif (ret) {\n>> +\t\trollback_lock_file(&lock);\n>> +\t\treturn ret;\n>> +\t}\n>> +\n>> +\treturn write_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n> \n> Likewise, I do not see much point in saying the_repository->index; the_index\n> is a perfectly fine short-hand.\n> \n> -%<-\n>> +static int do_merge_one_file(struct index_state *istate,\n>> +\t\t\t     const struct object_id *orig_blob,\n>> +\t\t\t     const struct object_id *our_blob,\n>> +\t\t\t     const struct object_id *their_blob, const char *path,\n>> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n>> +{\n>> +\tint ret, i, dest;\n>> +\tmmbuffer_t result = {NULL, 0};\n>> +\tmmfile_t mmfs[3];\n>> +\tstruct ll_merge_options merge_opts = {0};\n>> +\tstruct cache_entry *ce;\n>> +\n>> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n>> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n>> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n>> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n>> +\n>> +\tread_mmblob(mmfs + 1, our_blob);\n>> +\tread_mmblob(mmfs + 2, their_blob);\n>> +\n>> +\tif (orig_blob) {\n>> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n>> +\t\tread_mmblob(mmfs + 0, orig_blob);\n>> +\t} else {\n>> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n>> +\t\tread_mmblob(mmfs + 0, &null_oid);\n>> +\t}\n>> +\n>> +\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n>> +\tret = ll_merge(&result, path,\n>> +\t\t       mmfs + 0, \"orig\",\n>> +\t\t       mmfs + 1, \"our\",\n>> +\t\t       mmfs + 2, \"their\",\n>> +\t\t       istate, &merge_opts);\n>> +\n>> +\tfor (i = 0; i < 3; i++)\n>> +\t\tfree(mmfs[i].ptr);\n>> +\n>> +\tif (ret > 127 || !orig_blob)\n>> +\t\tret = error(_(\"content conflict in %s\"), path);\n> \n> The original only checked if ret is zero or non-zero; here we\n> require ret to be large.  Intended?  \n> \n> ll_merge() that called ll_xdl_merge() (i.e. the most common case)\n> would return the value returned from xdl_merge(), which can be -1\n> when we got an error before calling xdl_do_merge().  xdl_do_merge()\n> in turn can return -1.  The most common case returns the value\n> returned from xdl_cleanup_merge(), which is 0 for clean merge, and\n> any positive integer (not clipped to 127 or 128) for conflicted one.\n> \n\nHuh, not sure why I did this, and I'm puzzled that it did not broke\nanything.\n\n>> +\t/* Create the working tree file, using \"our tree\" version from\n>> +\t   the index, and then store the result of the merge. */\n> \n> Style. (cf. Documentation/CodingGuidelines).\n> \n>> +\tce = index_file_exists(istate, path, strlen(path), 0);\n>> +\tif (!ce)\n>> +\t\tBUG(\"file is not present in the cache?\");\n>> +\n>> +\tunlink(path);\n>> +\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n>> +\twrite_in_full(dest, result.ptr, result.size);\n> \n> If open() fails, we write to a bogus file descriptor here.\n> \n>> +\tclose(dest);\n>> +\n>> +\tfree(result.ptr);\n>> +\n>> +\tif (ret && our_mode != their_mode)\n>> +\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n>> +\t\t\t     orig_mode, our_mode, their_mode, path);\n>> +\tif (ret)\n>> +\t\treturn 1;\n> \n> What is the error returning convention around here?  Our usual\n> convention is that 0 signals a success, and negative reports an\n> error.  Returning the value returned from add_file_to_index() below,\n> and error() above, are consistent with the convention, but this one\n> returns 1 that is not.  When deviating from convention, it needs to\n> be documented for the callers in a comment before the function\n> definition.\n> \n\nI stayed to close to the shell script on this one…\n\nNote that this is not the case for \"resolve\" and \"octopus\", they use the\nconvention for merge backends, documented in builtin/merge.c:\n\n> \t\t/*\n> \t\t * The backend exits with 1 when conflicts are\n> \t\t * left to be resolved, with 2 when it does not\n> \t\t * handle the given merge at all.\n> \t\t */\n\n(In practice, it looks like any non-zero value lower than 2 indicates a\nmerge conflict, any value greater or equal to 2 is a general failure.)\n\n>> +\n>> +\treturn add_file_to_index(istate, path, 0);\n>> +}\n> \n> \n> \n>> +int merge_strategies_one_file(struct repository *r,\n>> +\t\t\t      const struct object_id *orig_blob,\n>> +\t\t\t      const struct object_id *our_blob,\n>> +\t\t\t      const struct object_id *their_blob, const char *path,\n>> +\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>> +\t\t\t      unsigned int their_mode)\n>> +{\n>> +\tif (orig_blob &&\n>> +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n>> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n>> +\t\t/* Deleted in both or deleted in one and unchanged in\n>> +\t\t   the other */\n>> +\t\treturn merge_one_file_deleted(r->index,\n>> +\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n>> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n>> +\telse if (!orig_blob && our_blob && !their_blob) {\n>> +\t\t/* Added in one.  The other side did not add and we\n>> +\t\t   added so there is nothing to be done, except making\n>> +\t\t   the path merged. */\n>> +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n>> +\t} else if (!orig_blob && !our_blob && their_blob) {\n>> +\t\tprintf(_(\"Adding %s\\n\"), path);\n>> +\n>> +\t\tif (file_exists(path))\n>> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n>> +\n>> +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n>> +\t\t\treturn 1;\n>> +\t\treturn checkout_from_index(r->index, path);\n>> +\t} else if (!orig_blob && our_blob && their_blob &&\n>> +\t\t   oideq(our_blob, their_blob)) {\n>> +\t\t/* Added in both, identically (check for same\n>> +\t\t   permissions). */\n>> +\t\tif (our_mode != their_mode)\n>> +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n>> +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n>> +\t\t\t\t     path, our_mode, their_mode);\n>> +\n>> +\t\tprintf(_(\"Adding %s\\n\"), path);\n>> +\n>> +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n>> +\t\t\treturn 1;\n>> +\t\treturn checkout_from_index(r->index, path);\n>> +\t} else if (our_blob && their_blob)\n>> +\t\t/* Modified in both, but differently. */\n>> +\t\treturn do_merge_one_file(r->index,\n>> +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n>> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n>> +\telse {\n>> +\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n>> +\n>> +\t\tif (orig_blob)\n>> +\t\t\torig_hex = oid_to_hex(orig_blob);\n>> +\t\tif (our_blob)\n>> +\t\t\tour_hex = oid_to_hex(our_blob);\n>> +\t\tif (their_blob)\n>> +\t\t\ttheir_hex = oid_to_hex(their_blob);\n> \n> Prepare three char [] buffers and use oid_to_hex_r() instead,\n> instead of relying that we'd have sufficient number of entries in\n> the rotating buffer.\n> \n>> +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n>> +\t\t\t     path, orig_hex, our_hex, their_hex);\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n>> diff --git a/merge-strategies.h b/merge-strategies.h\n>> new file mode 100644\n>> index 0000000000..b527d145c7\n>> --- /dev/null\n>> +++ b/merge-strategies.h\n>> @@ -0,0 +1,13 @@\n>> +#ifndef MERGE_STRATEGIES_H\n>> +#define MERGE_STRATEGIES_H\n>> +\n>> +#include \"object.h\"\n>> +\n>> +int merge_strategies_one_file(struct repository *r,\n>> +\t\t\t      const struct object_id *orig_blob,\n>> +\t\t\t      const struct object_id *our_blob,\n>> +\t\t\t      const struct object_id *their_blob, const char *path,\n>> +\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>> +\t\t\t      unsigned int their_mode);\n>> +\n>> +#endif /* MERGE_STRATEGIES_H */\n>> diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\n>> index 2eddcc7664..5fb74e39a0 100755\n>> --- a/t/t6415-merge-dir-to-symlink.sh\n>> +++ b/t/t6415-merge-dir-to-symlink.sh\n>> @@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n>>  \ttest -h a/b\n>>  '\n>>  \n>> -test_expect_failure 'do not lose untracked in merge (resolve)' '\n>> +test_expect_success 'do not lose untracked in merge (resolve)' '\n>>  \tgit reset --hard &&\n>>  \tgit checkout baseline^0 &&\n>>  \t>a/b/c/e &&\n\n"},{"id":"404917","messageId":"6e16673b-2c66-ddaf-5dec-abdd770adcff@gmail.com","threadId":"53755","inReplyTo":"xmqqft81cidt.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 03/11] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-09-02T15:37:04Z","receivedAt":"2020-09-02T15:37:24Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Le 01/09/2020 à 23:11, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n>> merge-one-file', they delegate the work to another git command, `git\n>> merge-index', that will loop over files in the index and call the\n>> specified command.  Unfortunately, these functions are not part of\n>> libgit.a, which means that once rewritten, the strategies would still\n>> have to invoke `merge-one-file' by spawning a new process first.\n>>\n>> To avoid this, this moves merge_one_path(), merge_all(), and their\n>> helpers to merge-strategies.c.  They also take a callback to dictate\n>> what they should do for each file.  For now, only one launching a new\n>> process is defined to preserve the behaviour of the builtin version.\n> \n> ... of the \"builtin\" version?  I thought this series is introducing\n> a new builtin version?  Puzzled...\n> \n\n`merge-index' is already a builtin, this step libifies it.  Its core\nfeature is to call repeatedly a command (usually it's\n`git-merge-one-file'), but the new version will call a callback instead,\nso its behaviour is not hardcoded.  This patch only provides a callback\nstarting a new command to preserve its behaviour.\n\nPerhaps rewording the last sentence like this would be better?\n\n  For now, only one launching a new process is defined, to preserve the\nbehaviour of `merge-index'.\n\n"},{"id":"406938","messageId":"20201005122646.27994-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20200901105705.6059-1-alban.gruin@gmail.com","subject":"[PATCH v3 00/11] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:35Z","receivedAt":"2020-10-05T12:27:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In a effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on it.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without any changes.\n\nThis series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).  The\ntip is tagged as \"rewrite-merge-strategies-v3\" at\nhttps://github.com/agrn/git.\n\nChanges since v2:\n\n - Enable `USE_THE_INDEX_COMPATIBILITY_MACROS' in merge-one-file.c and\n   use read_cache() and hold_locked_index() instead of repo_read_index()\n   and repo_hold_locked_index() to improve readability.\n\n - Move file mode parsing to its own function in merge-one-file.c.\n\n - Improve IO errors handling in do_merge_one_file().\n\n - Return -1 instead of 1 when erroring out in do_merge_one_file() and\n   merge_strategies_one_file().\n\n - Use oid_to_hex_r() instead of oid_to_hex() in do_merge_one_file().\n\n - Reformat multilines comments.\n\n - Reworded a sentence in commit 3/11.\n\nAlban Gruin (11):\n  t6027: modernise tests\n  merge-one-file: rewrite in C\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: don't fork if the requested program is\n    `git-merge-one-file'\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Makefile                        |   7 +-\n builtin.h                       |   3 +\n builtin/merge-index.c           | 102 ++----\n builtin/merge-octopus.c         |  69 ++++\n builtin/merge-one-file.c        |  92 +++++\n builtin/merge-recursive.c       |  16 +-\n builtin/merge-resolve.c         |  69 ++++\n builtin/merge.c                 |   9 +-\n cache.h                         |   2 +-\n git-merge-octopus.sh            | 112 ------\n git-merge-one-file.sh           | 167 ---------\n git-merge-resolve.sh            |  54 ---\n git.c                           |   3 +\n merge-strategies.c              | 613 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  44 +++\n merge.c                         |  12 +\n sequencer.c                     |  16 +-\n t/t6407-merge-binary.sh         |  27 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 19 files changed, 972 insertions(+), 447 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v2:\n 1:  28c8fd11b6 =  1:  08c7df596a t6027: modernise tests\n 2:  f5ab0fdf0a !  2:  ce911c99c0 merge-one-file: rewrite in C\n    @@ builtin/merge-one-file.c (new)\n     + * that might change the tree layout.\n     + */\n     +\n    ++#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     +#include \"cache.h\"\n     +#include \"builtin.h\"\n     +#include \"lockfile.h\"\n    @@ builtin/merge-one-file.c (new)\n     +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n     +\t\"Blob ids and modes should be empty for missing files.\";\n     +\n    ++static int read_mode(const char *name, const char *arg, unsigned int *mode)\n    ++{\n    ++\tchar *last;\n    ++\tint ret = 0;\n    ++\n    ++\t*mode = strtol(arg, &last, 8);\n    ++\n    ++\tif (*last)\n    ++\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n    ++\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n    ++\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n    ++\n    ++\treturn ret;\n    ++}\n    ++\n     +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n     +{\n     +\tstruct object_id orig_blob, our_blob, their_blob,\n    @@ builtin/merge-one-file.c (new)\n     +\tif (argc != 8)\n     +\t\tusage(builtin_merge_one_file_usage);\n     +\n    -+\tif (repo_read_index(the_repository) < 0)\n    ++\tif (read_cache() < 0)\n     +\t\tdie(\"invalid index\");\n     +\n    -+\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n    ++\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n     +\n     +\tif (!get_oid(argv[1], &orig_blob)) {\n     +\t\tp_orig_blob = &orig_blob;\n    -+\t\torig_mode = strtol(argv[5], NULL, 8);\n    -+\n    -+\t\tif (!(S_ISREG(orig_mode) || S_ISDIR(orig_mode) || S_ISLNK(orig_mode)))\n    -+\t\t\tret |= error(_(\"invalid 'orig' mode: %o\"), orig_mode);\n    ++\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n     +\t}\n     +\n     +\tif (!get_oid(argv[2], &our_blob)) {\n     +\t\tp_our_blob = &our_blob;\n    -+\t\tour_mode = strtol(argv[6], NULL, 8);\n    -+\n    -+\t\tif (!(S_ISREG(our_mode) || S_ISDIR(our_mode) || S_ISLNK(our_mode)))\n    -+\t\t\tret |= error(_(\"invalid 'our' mode: %o\"), our_mode);\n    ++\t\tret = read_mode(\"our\", argv[6], &our_mode);\n     +\t}\n     +\n     +\tif (!get_oid(argv[3], &their_blob)) {\n     +\t\tp_their_blob = &their_blob;\n    -+\t\ttheir_mode = strtol(argv[7], NULL, 8);\n    -+\n    -+\t\tif (!(S_ISREG(their_mode) || S_ISDIR(their_mode) || S_ISLNK(their_mode)))\n    -+\t\t\tret = error(_(\"invalid 'their' mode: %o\"), their_mode);\n    ++\t\tret = read_mode(\"their\", argv[7], &their_mode);\n     +\t}\n     +\n     +\tif (ret)\n    @@ builtin/merge-one-file.c (new)\n     +\n     +\tif (ret) {\n     +\t\trollback_lock_file(&lock);\n    -+\t\treturn ret;\n    ++\t\treturn !!ret;\n     +\t}\n     +\n    -+\treturn write_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n    ++\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n     +}\n     \n      ## git-merge-one-file.sh (deleted) ##\n    @@ merge-strategies.c (new)\n     +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n     +{\n     +\tint ret, i, dest;\n    ++\tssize_t written;\n     +\tmmbuffer_t result = {NULL, 0};\n     +\tmmfile_t mmfs[3];\n     +\tstruct ll_merge_options merge_opts = {0};\n    @@ merge-strategies.c (new)\n     +\tfor (i = 0; i < 3; i++)\n     +\t\tfree(mmfs[i].ptr);\n     +\n    -+\tif (ret > 127 || !orig_blob)\n    -+\t\tret = error(_(\"content conflict in %s\"), path);\n    ++\tif (ret < 0) {\n    ++\t\tfree(result.ptr);\n    ++\t\treturn error(_(\"Failed to execute internal merge\"));\n    ++\t}\n     +\n    -+\t/* Create the working tree file, using \"our tree\" version from\n    -+\t   the index, and then store the result of the merge. */\n    ++\t/*\n    ++\t * Create the working tree file, using \"our tree\" version from\n    ++\t * the index, and then store the result of the merge.\n    ++\t */\n     +\tce = index_file_exists(istate, path, strlen(path), 0);\n     +\tif (!ce)\n     +\t\tBUG(\"file is not present in the cache?\");\n     +\n     +\tunlink(path);\n    -+\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n    -+\twrite_in_full(dest, result.ptr, result.size);\n    ++\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n    ++\t\tfree(result.ptr);\n    ++\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n    ++\t}\n    ++\n    ++\twritten = write_in_full(dest, result.ptr, result.size);\n     +\tclose(dest);\n     +\n     +\tfree(result.ptr);\n     +\n    -+\tif (ret && our_mode != their_mode)\n    ++\tif (written < 0)\n    ++\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n    ++\n    ++\tif (ret != 0 || !orig_blob)\n    ++\t\tret = error(_(\"content conflict in %s\"), path);\n    ++\tif (our_mode != their_mode)\n     +\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n     +\t\t\t     orig_mode, our_mode, their_mode, path);\n     +\tif (ret)\n    -+\t\treturn 1;\n    ++\t\treturn -1;\n     +\n     +\treturn add_file_to_index(istate, path, 0);\n     +}\n    @@ merge-strategies.c (new)\n     +\tif (orig_blob &&\n     +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n     +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n    -+\t\t/* Deleted in both or deleted in one and unchanged in\n    -+\t\t   the other */\n    ++\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n     +\t\treturn merge_one_file_deleted(r->index,\n     +\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n     +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n     +\telse if (!orig_blob && our_blob && !their_blob) {\n    -+\t\t/* Added in one.  The other side did not add and we\n    -+\t\t   added so there is nothing to be done, except making\n    -+\t\t   the path merged. */\n    ++\t\t/*\n    ++\t\t * Added in one.  The other side did not add and we\n    ++\t\t * added so there is nothing to be done, except making\n    ++\t\t * the path merged.\n    ++\t\t */\n     +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n     +\t} else if (!orig_blob && !our_blob && their_blob) {\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n    @@ merge-strategies.c (new)\n     +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n     +\n     +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n    -+\t\t\treturn 1;\n    ++\t\t\treturn -1;\n     +\t\treturn checkout_from_index(r->index, path);\n     +\t} else if (!orig_blob && our_blob && their_blob &&\n     +\t\t   oideq(our_blob, their_blob)) {\n    -+\t\t/* Added in both, identically (check for same\n    -+\t\t   permissions). */\n    ++\t\t/* Added in both, identically (check for same permissions). */\n     +\t\tif (our_mode != their_mode)\n     +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n     +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n    @@ merge-strategies.c (new)\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n     +\n     +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n    -+\t\t\treturn 1;\n    ++\t\t\treturn -1;\n     +\t\treturn checkout_from_index(r->index, path);\n     +\t} else if (our_blob && their_blob)\n     +\t\t/* Modified in both, but differently. */\n    @@ merge-strategies.c (new)\n     +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n     +\t\t\t\t\t orig_mode, our_mode, their_mode);\n     +\telse {\n    -+\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n    ++\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n    ++\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n     +\n     +\t\tif (orig_blob)\n    -+\t\t\torig_hex = oid_to_hex(orig_blob);\n    ++\t\t\toid_to_hex_r(orig_hex, orig_blob);\n     +\t\tif (our_blob)\n    -+\t\t\tour_hex = oid_to_hex(our_blob);\n    ++\t\t\toid_to_hex_r(our_hex, our_blob);\n     +\t\tif (their_blob)\n    -+\t\t\ttheir_hex = oid_to_hex(their_blob);\n    ++\t\t\toid_to_hex_r(their_hex, their_blob);\n     +\n     +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n     +\t\t\t     path, orig_hex, our_hex, their_hex);\n 3:  7f3ce7da17 !  3:  7f0999f5a3 merge-index: libify merge_one_path() and merge_all()\n    @@ Commit message\n     \n         To avoid this, this moves merge_one_path(), merge_all(), and their\n         helpers to merge-strategies.c.  They also take a callback to dictate\n    -    what they should do for each file.  For now, only one launching a new\n    -    process is defined to preserve the behaviour of the builtin version.\n    +    what they should do for each file.  For now, to preserve the behaviour\n    +    of `merge-index', only one callback, launching a new process, is\n    +    defined.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n 4:  07e6a6aaef =  4:  c0bc05406d merge-index: don't fork if the requested program is `git-merge-one-file'\n 5:  117d4fc840 =  5:  cbfe192982 merge-resolve: rewrite in C\n 6:  4fc955962b =  6:  35e386f626 merge-recursive: move better_branch_name() to merge.c\n 7:  e7b9e15b34 !  7:  41eb0f7199 merge-octopus: rewrite in C\n    @@ Makefile: BUILTIN_OBJS += builtin/mailsplit.o\n      BUILTIN_OBJS += builtin/merge-recursive.o\n     \n      ## builtin.h ##\n    -@@ builtin.h: int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n    +@@ builtin.h: int cmd_maintenance(int argc, const char **argv, const char *prefix);\n      int cmd_merge(int argc, const char **argv, const char *prefix);\n      int cmd_merge_base(int argc, const char **argv, const char *prefix);\n      int cmd_merge_index(int argc, const char **argv, const char *prefix);\n    @@ builtin/merge-octopus.c (new)\n     +\tif (repo_read_index(the_repository) < 0)\n     +\t\tdie(\"corrupted cache\");\n     +\n    -+\t/* The first parameters up to -- are merge bases; the rest are\n    -+\t * heads. */\n    ++\t/*\n    ++\t * The first parameters up to -- are merge bases; the rest are\n    ++\t * heads.\n    ++\t */\n     +\tfor (i = 1; i < argc; i++) {\n     +\t\tif (strcmp(argv[i], \"--\") == 0)\n     +\t\t\tsep_seen = 1;\n    @@ builtin/merge-octopus.c (new)\n     +\t\t}\n     +\t}\n     +\n    -+\t/* Reject if this is not an octopus -- resolve should be used\n    -+\t * instead. */\n    ++\t/*\n    ++\t * Reject if this is not an octopus -- resolve should be used\n    ++\t * instead.\n    ++\t */\n     +\tif (commit_list_count(remotes) < 2)\n     +\t\treturn 2;\n     +\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\tint can_ff = 1;\n     +\n     +\t\tif (ret) {\n    -+\t\t\t/* We allow only last one to have a\n    -+\t\t\t   hand-resolvable conflicts.  Last round failed\n    -+\t\t\t   and we still had a head to merge. */\n    ++\t\t\t/*\n    ++\t\t\t * We allow only last one to have a\n    ++\t\t\t * hand-resolvable conflicts.  Last round failed\n    ++\t\t\t * and we still had a head to merge.\n    ++\t\t\t */\n     +\t\t\tputs(_(\"Automated merge did not work.\"));\n     +\t\t\tputs(_(\"Should not be doing an octopus.\"));\n     +\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t}\n     +\n     +\t\tif (!non_ff_merge && can_ff) {\n    -+\t\t\t/* The first head being merged was a\n    -+\t\t\t   fast-forward.  Advance the reference commit\n    -+\t\t\t   to the head being merged, and use that tree\n    -+\t\t\t   as the intermediate result of the merge.  We\n    -+\t\t\t   still need to count this as part of the\n    -+\t\t\t   parent set. */\n    ++\t\t\t/*\n    ++\t\t\t * The first head being merged was a\n    ++\t\t\t * fast-forward.  Advance the reference commit\n    ++\t\t\t * to the head being merged, and use that tree\n    ++\t\t\t * as the intermediate result of the merge.  We\n    ++\t\t\t * still need to count this as part of the\n    ++\t\t\t * parent set.\n    ++\t\t\t */\n     +\t\t\tstruct object_id oids[2];\n     +\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n     +\n 8:  cd0662201d =  8:  8f6c1ac057 merge: use the \"resolve\" strategy without forking\n 9:  0525ff0183 =  9:  b1125261d1 merge: use the \"octopus\" strategy without forking\n10:  6fbf599ba4 = 10:  8d0932fd02 sequencer: use the \"resolve\" strategy without forking\n11:  2c2dc3cc62 = 11:  e304723957 sequencer: use the \"octopus\" merge strategy without forking\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406939","messageId":"20201005122646.27994-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 03/11] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:38Z","receivedAt":"2020-10-05T12:27:43Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves merge_one_path(), merge_all(), and their\nhelpers to merge-strategies.c.  They also take a callback to dictate\nwhat they should do for each file.  For now, to preserve the behaviour\nof `merge-index', only one callback, launching a new process, is\ndefined.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 77 +++------------------------------\n merge-strategies.c    | 99 +++++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    | 17 ++++++++\n 3 files changed, 123 insertions(+), 70 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..6cb666cc78 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,11 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n-#include \"run-command.h\"\n-\n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n-\n-static int merge_entry(int pos, const char *path)\n-{\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n-\n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n-\n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n-\t}\n-}\n+#include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tconst char *pgm;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t      merge_program_cb, (void *)pgm);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex bbe6f48698..f0e30f5624 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -2,6 +2,7 @@\n #include \"dir.h\"\n #include \"ll-merge.h\"\n #include \"merge-strategies.h\"\n+#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n static int add_to_index_cacheinfo(struct index_state *istate,\n@@ -212,3 +213,101 @@ int merge_strategies_one_file(struct repository *r,\n \n \treturn 0;\n }\n+\n+int merge_program_cb(const struct object_id *orig_blob,\n+\t\t     const struct object_id *our_blob,\n+\t\t     const struct object_id *their_blob, const char *path,\n+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t     void *data)\n+{\n+\tchar ownbuf[3][GIT_MAX_HEXSZ] = {{0}};\n+\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n+\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n+\t\t\t\t    NULL };\n+\n+\tif (orig_blob)\n+\t\targuments[1] = oid_to_hex(orig_blob);\n+\tif (our_blob)\n+\t\targuments[2] = oid_to_hex(our_blob);\n+\tif (their_blob)\n+\t\targuments[3] = oid_to_hex(their_blob);\n+\n+\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n+\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n+\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n+\n+\treturn run_command_v_opt(arguments, 0);\n+}\n+\n+static int merge_entry(struct index_state *istate, int quiet, int pos,\n+\t\t       const char *path, merge_cb cb, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\treturn -2;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t   const char *path, merge_cb cb, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all(struct index_state *istate, int oneshot, int quiet,\n+\t      merge_cb cb, void *data)\n+{\n+\tint err = 0, i, ret;\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2) {\n+\t\t\tif (oneshot)\n+\t\t\t\terr++;\n+\t\t\telse\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex b527d145c7..cf78d7eaf4 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n \t\t\t      unsigned int their_mode);\n \n+typedef int (*merge_cb)(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_program_cb(const struct object_id *orig_blob,\n+\t\t     const struct object_id *our_blob,\n+\t\t     const struct object_id *their_blob, const char *path,\n+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t     void *data);\n+\n+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t   const char *path, merge_cb cb, void *data);\n+int merge_all(struct index_state *istate, int oneshot, int quiet,\n+\t      merge_cb cb, void *data);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406940","messageId":"20201005122646.27994-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 02/11] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:37Z","receivedAt":"2020-10-05T12:27:43Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_cache_entry();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_cache();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and ll_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are replaced by calls to\n   cache_file_exists();\n\n - calls to `update-index' are replaced by calls to add_file_to_cache().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   3 +-\n builtin.h                       |   1 +\n builtin/merge-one-file.c        |  92 ++++++++++++++\n git-merge-one-file.sh           | 167 -------------------------\n git.c                           |   1 +\n merge-strategies.c              | 214 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  13 ++\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 8 files changed, 324 insertions(+), 169 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex de53954590..6dfdb33cb2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -909,6 +908,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\n@@ -1094,6 +1094,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..4d2cd78856 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -178,6 +178,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..598338ba16\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,92 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file SHA1 (or empty)\n+ *   argv[2] - file in branch1 SHA1 (or empty)\n+ *   argv[3] - file in branch2 SHA1 (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+static int read_mode(const char *name, const char *arg, unsigned int *mode)\n+{\n+\tchar *last;\n+\tint ret = 0;\n+\n+\t*mode = strtol(arg, &last, 8);\n+\n+\tif (*last)\n+\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n+\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n+\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n+\n+\treturn ret;\n+}\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (!get_oid(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n+\t}\n+\n+\tif (!get_oid(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tret = read_mode(\"our\", argv[6], &our_mode);\n+\t}\n+\n+\tif (!get_oid(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\tret = read_mode(\"their\", argv[7], &their_mode);\n+\t}\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_strategies_one_file(the_repository,\n+\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n+\t\t\t\t\torig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex f1e8b56d99..a4d3f98094 100644\n--- a/git.c\n+++ b/git.c\n@@ -540,6 +540,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..bbe6f48698\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,214 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"ll-merge.h\"\n+#include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int add_to_index_cacheinfo(struct index_state *istate,\n+\t\t\t\t  unsigned int mode,\n+\t\t\t\t  const struct object_id *oid, const char *path)\n+{\n+\tstruct cache_entry *ce;\n+\tint len, option;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(0);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n+\tif (add_index_entry(istate, ce, option))\n+\t\treturn error(_(\"%s: cannot add to the index\"), path);\n+\n+\treturn 0;\n+}\n+\n+static int checkout_from_index(struct index_state *istate, const char *path)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\tstruct cache_entry *ce;\n+\n+\tstate.istate = istate;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tce = index_file_exists(istate, path, strlen(path), 0);\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *orig_blob,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tssize_t written;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\tstruct ll_merge_options merge_opts = {0};\n+\tstruct cache_entry *ce;\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, &null_oid);\n+\t}\n+\n+\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n+\tret = ll_merge(&result, path,\n+\t\t       mmfs + 0, \"orig\",\n+\t\t       mmfs + 1, \"our\",\n+\t\t       mmfs + 2, \"their\",\n+\t\t       istate, &merge_opts);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"Failed to execute internal merge\"));\n+\t}\n+\n+\t/*\n+\t * Create the working tree file, using \"our tree\" version from\n+\t * the index, and then store the result of the merge.\n+\t */\n+\tce = index_file_exists(istate, path, strlen(path), 0);\n+\tif (!ce)\n+\t\tBUG(\"file is not present in the cache?\");\n+\n+\tunlink(path);\n+\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n+\t}\n+\n+\twritten = write_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (written < 0)\n+\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n+\n+\tif (ret != 0 || !orig_blob)\n+\t\tret = error(_(\"content conflict in %s\"), path);\n+\tif (our_mode != their_mode)\n+\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t     orig_mode, our_mode, their_mode, path);\n+\tif (ret)\n+\t\treturn -1;\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_strategies_one_file(struct repository *r,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob, const char *path,\n+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t      unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n+\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n+\t\treturn merge_one_file_deleted(r->index,\n+\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\telse if (!orig_blob && our_blob && !their_blob) {\n+\t\t/*\n+\t\t * Added in one.  The other side did not add and we\n+\t\t * added so there is nothing to be done, except making\n+\t\t * the path merged.\n+\t\t */\n+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\t/* Added in both, identically (check for same permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path);\n+\t} else if (our_blob && their_blob)\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(r->index,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\telse {\n+\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n+\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n+\n+\t\tif (orig_blob)\n+\t\t\toid_to_hex_r(orig_hex, orig_blob);\n+\t\tif (our_blob)\n+\t\t\toid_to_hex_r(our_hex, our_blob);\n+\t\tif (their_blob)\n+\t\t\toid_to_hex_r(their_hex, their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..b527d145c7\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,13 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+int merge_strategies_one_file(struct repository *r,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob, const char *path,\n+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t      unsigned int their_mode);\n+\n+#endif /* MERGE_STRATEGIES_H */\ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2eddcc7664..5fb74e39a0 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406941","messageId":"20201005122646.27994-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 01/11] t6027: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:36Z","receivedAt":"2020-10-05T12:27:45Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6027 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6407-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\nindex 4e6c7cb77e..071d3f7343 100755\n--- a/t/t6407-merge-binary.sh\n+++ b/t/t6407-merge-binary.sh\n@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -35,33 +34,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406942","messageId":"20201005122646.27994-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 08/11] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:43Z","receivedAt":"2020-10-05T12:27:54Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..ddfefd8ce3 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -740,7 +741,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n-\t} else {\n+\t} else if (!strcmp(strategy, \"resolve\"))\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n+\telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n \t\t\t\t\t common, head_arg, remoteheads);\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406943","messageId":"20201005122646.27994-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 11/11] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:46Z","receivedAt":"2020-10-05T12:28:10Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ff411d54af..746afad930 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2005,6 +2005,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406944","messageId":"20201005122646.27994-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 07/11] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:42Z","receivedAt":"2020-10-05T12:28:12Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes(), and is moved from cmd_merge_octopus() to\n   merge_octopus().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_strategies_octopus().\n\nHere to, merge_strategies_octopus() takes two commit lists and a string\nto reduce frictions when try_merge_strategies() will be modified to call\nit directly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  69 ++++++++++++++\n git-merge-octopus.sh    | 112 ----------------------\n git.c                   |   1 +\n merge-strategies.c      | 204 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 279 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3cc6b192f1..2b2bdffafe 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -600,7 +600,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1093,6 +1092,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 35e91c16d0..50225404a0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -176,6 +176,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..abf0981fe8\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,69 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"corrupted cache\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tget_oid(argv[i], &oid);\n+\n+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\t\tif (sep_seen)\n+\t\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\t\telse\n+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Reject if this is not an octopus -- resolve should be used\n+\t * instead.\n+\t */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 7d19d37951..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 64a1a1de41..d51fb5d2bf 100644\n--- a/git.c\n+++ b/git.c\n@@ -539,6 +539,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 6b4b3d03a6..37c662094e 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"ll-merge.h\"\n #include \"lockfile.h\"\n@@ -407,3 +408,206 @@ int merge_strategies_resolve(struct repository *r,\n \trollback_lock_file(&lock);\n \treturn 2;\n }\n+\n+static int fast_forward(struct repository *r, const struct object_id *oids,\n+\t\t\tint nr, int aggressive)\n+{\n+\tint i;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trepo_read_index_preload(r, NULL, 0);\n+\tif (refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL))\n+\t\treturn -1;\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct tree *tree;\n+\t\ttree = parse_tree_indirect(oids + i);\n+\t\tif (parse_tree(tree))\n+\t\t\treturn -1;\n+\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n+\t}\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL);\n+\tif (!ret)\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint non_ff_merge = 0, ret = 0, references = 1;\n+\tstruct commit **reference_commit;\n+\tstruct tree *reference_tree;\n+\tstruct commit_list *j;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n+\treference_commit[0] = lookup_commit_reference(r, &head);\n+\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n+\n+\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n+\tfor (j = remotes; j && j->item; j = j->next) {\n+\t\tstruct commit *c = j->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct commit_list *common, *k;\n+\t\tchar *branch_name;\n+\t\tint can_ff = 1;\n+\n+\t\tif (ret) {\n+\t\t\t/*\n+\t\t\t * We allow only last one to have a\n+\t\t\t * hand-resolvable conflicts.  Last round failed\n+\t\t\t * and we still had a head to merge.\n+\t\t\t */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common)\n+\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n+\n+\t\tif (k) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!non_ff_merge) {\n+\t\t\tint i;\n+\n+\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n+\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n+\t\t\t\t\t       &reference_commit[i]->object.oid);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!non_ff_merge && can_ff) {\n+\t\t\t/*\n+\t\t\t * The first head being merged was a\n+\t\t\t * fast-forward.  Advance the reference commit\n+\t\t\t * to the head being merged, and use that tree\n+\t\t\t * as the intermediate result of the merge.  We\n+\t\t\t * still need to count this as part of the\n+\t\t\t * parent set.\n+\t\t\t */\n+\t\t\tstruct object_id oids[2];\n+\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\t\t\toidcpy(oids, &head);\n+\t\t\toidcpy(oids + 1, oid);\n+\n+\t\t\tret = fast_forward(r, oids, 2, 0);\n+\t\t\tif (ret) {\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\treferences = 0;\n+\t\t\twrite_tree(r, &reference_tree);\n+\t\t} else {\n+\t\t\tint i = 0;\n+\t\t\tstruct tree *next = NULL;\n+\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n+\n+\t\t\tnon_ff_merge = 1;\n+\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\t\t\tfor (k = common; k; k = k->next)\n+\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n+\n+\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n+\t\t\toidcpy(oids + (i++), oid);\n+\n+\t\t\tif (fast_forward(r, oids, i, 1)) {\n+\t\t\t\tret = 2;\n+\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tif (write_tree(r, &next)) {\n+\t\t\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\t\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\t\t\tret = !!merge_all(r->index, 0, 0, merge_one_file_cb, r);\n+\t\t\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\t\t\twrite_tree(r, &next);\n+\t\t\t}\n+\n+\t\t\treference_tree = next;\n+\t\t}\n+\n+\t\treference_commit[references++] = c;\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\t}\n+\n+out:\n+\tfree(reference_commit);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 778f8ce9d6..938411a04e 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -37,5 +37,8 @@ int merge_all(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406945","messageId":"20201005122646.27994-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 10/11] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:45Z","receivedAt":"2020-10-05T12:28:18Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e8676e965f..ff411d54af 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -33,6 +33,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2000,9 +2001,15 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406946","messageId":"20201005122646.27994-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 06/11] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:41Z","receivedAt":"2020-10-05T12:28:28Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"get_better_branch_name() will be used by rebase-octopus once it is\nrewritten in C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..5fa0ed8d1a 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1928,7 +1928,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 5fb88af102..801d673c5f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406947","messageId":"20201005122646.27994-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 04/11] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:39Z","receivedAt":"2020-10-05T12:28:31Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' has been rewritten and libified, this teaches\n`merge-index' to call merge_strategies_one_file() without forking using\na new callback, merge_one_file_cb().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 29 +++++++++++++++++++++++++++--\n merge-strategies.c    | 11 +++++++++++\n merge-strategies.h    |  6 ++++++\n 3 files changed, 44 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 6cb666cc78..19fff9a113 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,11 +1,15 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \tconst char *pgm;\n+\tvoid *data;\n+\tmerge_cb merge_action;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -26,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n+\t\tmerge_action = merge_one_file_cb;\n+\t\tdata = (void *)the_repository;\n+\n+\t\tsetup_work_tree();\n+\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\t} else {\n+\t\tmerge_action = merge_program_cb;\n+\t\tdata = (void *)pgm;\n+\t}\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -36,13 +52,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n-\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n+\t\t\t\t\t\t merge_action, data);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n-\t\t\t\t      merge_program_cb, (void *)pgm);\n+\t\t\t\t      merge_action, data);\n+\t}\n+\n+\tif (merge_action == merge_one_file_cb) {\n+\t\tif (err) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n \t}\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex f0e30f5624..c022ba9748 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -214,6 +214,17 @@ int merge_strategies_one_file(struct repository *r,\n \treturn 0;\n }\n \n+int merge_one_file_cb(const struct object_id *orig_blob,\n+\t\t      const struct object_id *our_blob,\n+\t\t      const struct object_id *their_blob, const char *path,\n+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t      void *data)\n+{\n+\treturn merge_strategies_one_file((struct repository *)data,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+}\n+\n int merge_program_cb(const struct object_id *orig_blob,\n \t\t     const struct object_id *our_blob,\n \t\t     const struct object_id *their_blob, const char *path,\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex cf78d7eaf4..40e175ca39 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -16,6 +16,12 @@ typedef int (*merge_cb)(const struct object_id *orig_blob,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_cb(const struct object_id *orig_blob,\n+\t\t      const struct object_id *our_blob,\n+\t\t      const struct object_id *their_blob, const char *path,\n+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t      void *data);\n+\n int merge_program_cb(const struct object_id *orig_blob,\n \t\t     const struct object_id *our_blob,\n \t\t     const struct object_id *their_blob, const char *path,\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406948","messageId":"20201005122646.27994-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 09/11] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:44Z","receivedAt":"2020-10-05T12:28:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ddfefd8ce3..02a2367647 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -744,6 +744,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\"))\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\telse if (!strcmp(strategy, \"octopus\"))\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"406949","messageId":"20201005122646.27994-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v3 05/11] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-05T12:26:40Z","receivedAt":"2020-10-05T12:28:42Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all() function.  A callback\n   function, merge_one_file_cb(), is added to allow it to call\n   merge_one_file() without forking.\n\nHere too, the index is read in cmd_merge_resolve(), but\nmerge_strategies_resolve() takes care of writing it back to the disk.\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |  2 +-\n builtin.h               |  1 +\n builtin/merge-resolve.c | 69 +++++++++++++++++++++++++++++++++\n git-merge-resolve.sh    | 54 --------------------------\n git.c                   |  1 +\n merge-strategies.c      | 85 +++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 7 files changed, 162 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex 6dfdb33cb2..3cc6b192f1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1097,6 +1096,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 4d2cd78856..35e91c16d0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -180,6 +180,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..59f734473b\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,69 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, is_baseless = 1, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(the_repository) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/* The first parameters up to -- are merge bases; the rest are\n+\t * heads. */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse if (remote) {\n+\t\t\t/* Give up if we are given two or more remotes.\n+\t\t\t * Not handling octopus. */\n+\t\t\treturn 2;\n+\t\t} else {\n+\t\t\tstruct object_id oid;\n+\n+\t\t\tget_oid(argv[i], &oid);\n+\t\t\tis_baseless &= sep_seen;\n+\n+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n+\t\t\t\tstruct commit *commit;\n+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\t\tif (sep_seen)\n+\t\t\t\t\tcommit_list_append(commit, &remote);\n+\t\t\t\telse\n+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (is_baseless)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 343fe7bccd..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex a4d3f98094..64a1a1de41 100644\n--- a/git.c\n+++ b/git.c\n@@ -544,6 +544,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex c022ba9748..6b4b3d03a6 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,8 +1,11 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n #include \"ll-merge.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int add_to_index_cacheinfo(struct index_state *istate,\n@@ -322,3 +325,85 @@ int merge_all(struct index_state *istate, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int add_tree(const struct object_id *oid, struct tree_desc *t)\n+{\n+\tstruct tree *tree;\n+\n+\ttree = parse_tree_indirect(oid);\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tint i = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct object_id head, oid;\n+\tstruct commit_list *j;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\trefresh_index(r->index, 0, NULL, NULL, NULL);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.update = 1;\n+\topts.merge = 1;\n+\topts.aggressive = 1;\n+\n+\tfor (j = bases; j && j->item; j = j->next) {\n+\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n+\t\t\tgoto out;\n+\t}\n+\n+\tif (head_arg && add_tree(&head, t + (i++)))\n+\t\tgoto out;\n+\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n+\t\tgoto out;\n+\n+\tif (i == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (i == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (i >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = i - 1;\n+\t}\n+\n+\tif (unpack_trees(i, t, &opts))\n+\t\tgoto out;\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all(r->index, 0, 0, merge_one_file_cb, r);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+\n+ out:\n+\trollback_lock_file(&lock);\n+\treturn 2;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 40e175ca39..778f8ce9d6 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_strategies_one_file(struct repository *r,\n@@ -33,4 +34,8 @@ int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all(struct index_state *istate, int oneshot, int quiet,\n \t      merge_cb cb, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.28.0.662.ge304723957\n\n"},{"id":"407008","messageId":"xmqqwo033wqm.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201005122646.27994-2-alban.gruin@gmail.com","subject":"Re: [PATCH v3 01/11] t6027: modernise tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-06T20:50:09Z","receivedAt":"2020-10-06T20:50:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> Some tests in t6027 uses a if/then/else to check if a command failed or\n\ns/uses/use/;\n\n> not, but we have the `test_must_fail' function to do it correctly for us\n> nowadays.\n\nMakes sense.  The patch text reads good, too.\n\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  t/t6407-merge-binary.sh | 27 ++++++---------------------\n>  1 file changed, 6 insertions(+), 21 deletions(-)\n>\n> diff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\n> index 4e6c7cb77e..071d3f7343 100755\n> --- a/t/t6407-merge-binary.sh\n> +++ b/t/t6407-merge-binary.sh\n> @@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n>  . ./test-lib.sh\n>  \n>  test_expect_success setup '\n> -\n>  \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n>  \tgit add m &&\n>  \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n> @@ -35,33 +34,19 @@ test_expect_success setup '\n>  '\n>  \n>  test_expect_success resolve '\n> -\n>  \trm -f a* m* &&\n>  \tgit reset --hard anchor &&\n> -\n> -\tif git merge -s resolve master\n> -\tthen\n> -\t\techo Oops, should not have succeeded\n> -\t\tfalse\n> -\telse\n> -\t\tgit ls-files -s >current\n> -\t\ttest_cmp expect current\n> -\tfi\n> +\ttest_must_fail git merge -s resolve master &&\n> +\tgit ls-files -s >current &&\n> +\ttest_cmp expect current\n>  '\n>  \n>  test_expect_success recursive '\n> -\n>  \trm -f a* m* &&\n>  \tgit reset --hard anchor &&\n> -\n> -\tif git merge -s recursive master\n> -\tthen\n> -\t\techo Oops, should not have succeeded\n> -\t\tfalse\n> -\telse\n> -\t\tgit ls-files -s >current\n> -\t\ttest_cmp expect current\n> -\tfi\n> +\ttest_must_fail git merge -s recursive master &&\n> +\tgit ls-files -s >current &&\n> +\ttest_cmp expect current\n>  '\n>  \n>  test_done\n"},{"id":"407009","messageId":"xmqqmu0z3tge.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201005122646.27994-3-alban.gruin@gmail.com","subject":"Re: [PATCH v3 02/11] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-06T22:01:05Z","receivedAt":"2020-10-06T22:01:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> This rewrites `git merge-one-file' from shell to C.  This port is not\n> completely straightforward: to save precious cycles by avoiding reading\n> and flushing the index repeatedly, write temporary files when an\n> operation can be performed in-memory, or allow other function to use the\n> rewrite without forking nor worrying about the index,...\n\nSo, the in-core index is still used, but when the contents of the in-core\nindex does not have to be written out disk, we just don't?  Makes sense.\n\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..598338ba16\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,92 @@\n> +/*\n> + * Builtin \"git merge-one-file\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> + *\n> + * This is the git per-file merge utility, called with\n> + *\n> + *   argv[1] - original file SHA1 (or empty)\n> + *   argv[2] - file in branch1 SHA1 (or empty)\n> + *   argv[3] - file in branch2 SHA1 (or empty)\n\nLet's modernize this comment while we are at it.\n\n    SHA1 -> \"object name\" (or \"blob object name\")\n\n> + *   argv[4] - pathname in repository\n> + *   argv[5] - original file mode (or empty)\n> + *   argv[6] - file in branch1 mode (or empty)\n> + *   argv[7] - file in branch2 mode (or empty)\n> + *\n> + * Handle some trivial cases. The _really_ trivial cases have been\n> + * handled already by git read-tree, but that one doesn't do any merges\n> + * that might change the tree layout.\n> + */\n> +\n> +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"lockfile.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_one_file_usage[] =\n> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n> +\t\"Blob ids and modes should be empty for missing files.\";\n> +\n> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n> +{\n> +\tchar *last;\n> +\tint ret = 0;\n> +\n> +\t*mode = strtol(arg, &last, 8);\n> +\n> +\tif (*last)\n> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n> +\n> +\treturn ret;\n> +}\n> +\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct object_id orig_blob, our_blob, their_blob,\n> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\n> +\tif (argc != 8)\n> +\t\tusage(builtin_merge_one_file_usage);\n> +\n> +\tif (read_cache() < 0)\n> +\t\tdie(\"invalid index\");\n> +\n> +\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n> +\n> +\tif (!get_oid(argv[1], &orig_blob)) {\n> +\t\tp_orig_blob = &orig_blob;\n> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n> +\t}\n\nargv[1] is defined as \"either the object name of the blob in the\ncommon ancestor, or an empty string\".  So you need to distinguish\nthree cases here, but you are only catching two.\n\n - argv[1] is an empty string; p_orig_blob can legitimately be left\n   NULL.\n\n - argv[1] is a valid blob object name.  orig_blob should be\n   populated and p_orig_blob should point at it.\n\n - argv[1] is garbage, names a non-blob object, or there is no such\n   object with that name.  Don't we want to catch it as a mistake?\n\nAlso, when argv[1] is an empty string, argv[5] must also be an empty\nstring, or we got a wrong input---don't we want to catch it as a\nmistake?\n\nThe third case needs a bit of thought.  For example, if $1 and $2\nare the same and points at a non-existent object, we know we won't\ncare because we only care about $3.  In a lazily-cloned repository,\nthat may matter---we would not want to fail even if we not have blob\n$1 and $2, as long as they are reasonably spelled a full hexadecimal\nobject name.  But we would want to fail if blob object named by $3\nis missing.\n\nOne way to achieve semantics closer to the above than the posted\npatch may be to tighten the parsing.  Instead of using \"anything\ngoes\" get_oid(), use get_oid_hex(), perhaps.\n\n> +\tif (!get_oid(argv[2], &our_blob)) {\n> +\t\tp_our_blob = &our_blob;\n> +\t\tret = read_mode(\"our\", argv[6], &our_mode);\n> +\t}\n> +\n> +\tif (!get_oid(argv[3], &their_blob)) {\n> +\t\tp_their_blob = &their_blob;\n> +\t\tret = read_mode(\"their\", argv[7], &their_mode);\n> +\t}\n> +\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = merge_strategies_one_file(the_repository,\n> +\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n> +\t\t\t\t\torig_mode, our_mode, their_mode);\n\nThat's a funny function name.  It's not like the function will be\ntaught different strategy to handle the three-way merge, no?  It\nprobably makes sense to name it after what it does, which is \"three\nway merge\".\n\n> +\tif (ret) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn !!ret;\n> +\t}\n> +\n> +\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n> +}\n\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> new file mode 100644\n> index 0000000000..bbe6f48698\n> --- /dev/null\n> +++ b/merge-strategies.c\n> @@ -0,0 +1,214 @@\n> +#include \"cache.h\"\n> +#include \"dir.h\"\n> +#include \"ll-merge.h\"\n> +#include \"merge-strategies.h\"\n> +#include \"xdiff-interface.h\"\n> +\n\n> +static int add_to_index_cacheinfo(struct index_state *istate,\n> +\t\t\t\t  unsigned int mode,\n> +\t\t\t\t  const struct object_id *oid, const char *path)\n> +{\n> +\tstruct cache_entry *ce;\n> +\tint len, option;\n> +\n> +\tif (!verify_path(path, mode))\n> +\t\treturn error(_(\"Invalid path '%s'\"), path);\n> +\n> +\tlen = strlen(path);\n> +\tce = make_empty_cache_entry(istate, len);\n> +\n> +\toidcpy(&ce->oid, oid);\n> +\tmemcpy(ce->name, path, len);\n> +\tce->ce_flags = create_ce_flags(0);\n> +\tce->ce_namelen = len;\n> +\tce->ce_mode = create_ce_mode(mode);\n> +\tif (assume_unchanged)\n> +\t\tce->ce_flags |= CE_VALID;\n> +\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n> +\tif (add_index_entry(istate, ce, option))\n> +\t\treturn error(_(\"%s: cannot add to the index\"), path);\n> +\n> +\treturn 0;\n> +}\n\nThe above correctly does 'git update-index --add --cacheinfo \"$6\"\n\"$2\" \"$4\"' but don't copy-and-paste existing code to do so.  Add one\npreliminary patch before everything else in the series to massage\nand extract add_cacheinfo() function out of builtin/update-index.c,\nmove it to somewhere common like read-cache.c and so that we can\ncall it from here.\n\n> +static int checkout_from_index(struct index_state *istate, const char *path)\n> +{\n> +\tstruct checkout state = CHECKOUT_INIT;\n> +\tstruct cache_entry *ce;\n> +\n> +\tstate.istate = istate;\n> +\tstate.force = 1;\n> +\tstate.base_dir = \"\";\n> +\tstate.base_dir_len = 0;\n> +\n> +\tce = index_file_exists(istate, path, strlen(path), 0);\n\nThis call is unfortunate for the reasons I mention later.\n\nBut if you must have this call, then you need to sanity check what\nyou get from index_file_exists().  ce must be a merged cache entry,\nso\n\n\tif (!ce || ce_stage(ce))\n\t\tBUG(...);\n\n> +\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n> +\t\treturn error(_(\"%s: cannot checkout file\"), path);\n> +\treturn 0;\n> +}\n> +\n> +static int merge_one_file_deleted(struct index_state *istate,\n> +\t\t\t\t  const struct object_id *orig_blob,\n> +\t\t\t\t  const struct object_id *our_blob,\n> +\t\t\t\t  const struct object_id *their_blob, const char *path,\n> +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif ((our_blob && orig_mode != our_mode) ||\n> +\t    (their_blob && orig_mode != their_mode))\n> +\t\treturn error(_(\"File %s deleted on one branch but had its \"\n> +\t\t\t       \"permissions changed on the other.\"), path);\n> +\n> +\tif (our_blob) {\n> +\t\tprintf(_(\"Removing %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\tremove_path(path);\n> +\t}\n> +\n> +\tif (remove_file_from_index(istate, path))\n> +\t\treturn error(\"%s: cannot remove from the index\", path);\n> +\treturn 0;\n\nIf the side that did not remove changed the mode, we don't silently\nremove but fail and give a chance to inspect the situation to the\nend user.  If we had the blob and it is removed by them, we give a\nmessage and only in that case we remove the file from the working\ntree, together with any leading directory that has become empty.\n\nAnd after that we make sure that the path is no longer in the\nindex.  The function removes entries for the path at all the stages,\nwhich is exactly what we want.\n\nOK.\n\n> +}\n> +\n> +static int do_merge_one_file(struct index_state *istate,\n> +\t\t\t     const struct object_id *orig_blob,\n> +\t\t\t     const struct object_id *our_blob,\n> +\t\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tint ret, i, dest;\n> +\tssize_t written;\n> +\tmmbuffer_t result = {NULL, 0};\n> +\tmmfile_t mmfs[3];\n> +\tstruct ll_merge_options merge_opts = {0};\n> +\tstruct cache_entry *ce;\n> +\n> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n> +\n> +\tread_mmblob(mmfs + 1, our_blob);\n> +\tread_mmblob(mmfs + 2, their_blob);\n> +\n> +\tif (orig_blob) {\n> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, orig_blob);\n> +\t} else {\n> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, &null_oid);\n> +\t}\n> +\n> +\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n> +\tret = ll_merge(&result, path,\n> +\t\t       mmfs + 0, \"orig\",\n> +\t\t       mmfs + 1, \"our\",\n> +\t\t       mmfs + 2, \"their\",\n> +\t\t       istate, &merge_opts);\n\nIs it correct to call into ll_merge() here?  The original used to\ncall \"git merge-file\" which called into xdl_merge().  Calling into\nll_merge() means the path is used to look up the attributes and use\nthe custom merge driver, which I am not offhand sure is what we want\nto see at this low level (and if it turns out to be a good idea, we\ndefinitely should explain the change of semantics in the proposed\nlog message for this commit).\n\n> +\tfor (i = 0; i < 3; i++)\n> +\t\tfree(mmfs[i].ptr);\n> +\n> +\tif (ret < 0) {\n> +\t\tfree(result.ptr);\n> +\t\treturn error(_(\"Failed to execute internal merge\"));\n> +\t}\n> +\n> +\t/*\n> +\t * Create the working tree file, using \"our tree\" version from\n> +\t * the index, and then store the result of the merge.\n> +\t */\n\nThe above is copied from the original, to explain what it did after\nthe comment, but it does not seem to match what the new code does.\n\n> +\tce = index_file_exists(istate, path, strlen(path), 0);\n> +\tif (!ce)\n> +\t\tBUG(\"file is not present in the cache?\");\n> +\n> +\tunlink(path);\n> +\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n> +\t\tfree(result.ptr);\n> +\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n> +\t}\n> +\n> +\twritten = write_in_full(dest, result.ptr, result.size);\n> +\tclose(dest);\n> +\n> +\tfree(result.ptr);\n> +\n> +\tif (written < 0)\n> +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n> +\n\nThis open(..., ce->ce_mode) call is way insufficient.\n\nThe comment we have above this part of the code talks about the\ndifficulty of doing this correctly in scripted version.  Creating a\nfile by 'git checkout-index -f --stage=2 -- \"$4\"' and reusing it to\nstore the merged contents was the cleanest and easiest way without\nhaving direct access to adjust_shared_perm() to create a working\ntree file with the correct permission bits.\n\nWe are writing in C, so we should be able to do much better than the\nscripted version, as we can later call adjust_shared_perm().\n\n> +\tif (ret != 0 || !orig_blob)\n> +\t\tret = error(_(\"content conflict in %s\"), path);\n> +\tif (our_mode != their_mode)\n> +\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n> +\t\t\t     orig_mode, our_mode, their_mode, path);\n> +\tif (ret)\n> +\t\treturn -1;\n> +\n> +\treturn add_file_to_index(istate, path, 0);\n> +}\n> +\n> +int merge_strategies_one_file(struct repository *r,\n> +\t\t\t      const struct object_id *orig_blob,\n> +\t\t\t      const struct object_id *our_blob,\n> +\t\t\t      const struct object_id *their_blob, const char *path,\n> +\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n> +\t\t\t      unsigned int their_mode)\n> +{\n\nIn a long if/else if/else if/.../else cascade, enclose all bodies in\nbraces, if any one of them has a multi-statement body, to avoid\nbeing distracting.\n\n> +\tif (orig_blob &&\n> +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n> +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n> +\t\treturn merge_one_file_deleted(r->index,\n> +\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n\nOK, we've already reviewed that function.\n\n> +\telse if (!orig_blob && our_blob && !their_blob) {\n> +\t\t/*\n> +\t\t * Added in one.  The other side did not add and we\n> +\t\t * added so there is nothing to be done, except making\n> +\t\t * the path merged.\n> +\t\t */\n> +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n\nOK, we've already reviewed that function.\n\n> +\t} else if (!orig_blob && !our_blob && their_blob) {\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n> +\n> +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n> +\t\t\treturn -1;\n> +\t\treturn checkout_from_index(r->index, path);\n\nYou did \"add_to_index_cacheinfo()\", so you MUST know which ce is to\nbe checked out.\n\nConsider if it is worth to teach add_to_index_cacheinfo() to give\nyou ce back and pass it to checkout_from_index(); that way, you do\nnot have to call index_file_exists() based on path in the function.\n\n> +\t} else if (!orig_blob && our_blob && their_blob &&\n> +\t\t   oideq(our_blob, their_blob)) {\n> +\t\t/* Added in both, identically (check for same permissions). */\n> +\t\tif (our_mode != their_mode)\n> +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n> +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n> +\t\t\t\t     path, our_mode, their_mode);\n> +\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n> +\t\t\treturn -1;\n> +\t\treturn checkout_from_index(r->index, path);\n\nDitto.\n\n> +\t} else if (our_blob && their_blob)\n> +\t\t/* Modified in both, but differently. */\n> +\t\treturn do_merge_one_file(r->index,\n> +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n> +\telse {\n> +\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n> +\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n> +\n> +\t\tif (orig_blob)\n> +\t\t\toid_to_hex_r(orig_hex, orig_blob);\n> +\t\tif (our_blob)\n> +\t\t\toid_to_hex_r(our_hex, our_blob);\n> +\t\tif (their_blob)\n> +\t\t\toid_to_hex_r(their_hex, their_blob);\n> +\n> +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n> +\t\t\t     path, orig_hex, our_hex, their_hex);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n\nI can see that this does go in the right direction.  With a bit more\nattention to details it would soon be production-ready quality.\n\nThanks.\n"},{"id":"407047","messageId":"nycvar.QRO.7.76.6.2010070855280.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"Re: [PATCH v3 00/11] Rewrite the remaining merge strategies from shell to C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-10-07T06:57:18Z","receivedAt":"2020-10-07T12:03:24Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Mon, 5 Oct 2020, Alban Gruin wrote:\n\n> In a effort to reduce the number of shell scripts in git's codebase, I\n> propose this patch series converting the two remaining merge strategies,\n> resolve and octopus, from shell to C.  This will enable slightly better\n> performance, better integration with git itself (no more forking to\n> perform these operations), better portability (Windows and shell scripts\n> don't mix well).\n>\n> Three scripts are actually converted: first git-merge-one-file.sh, then\n> git-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\n> are converted, but they also are modified to operate without forking,\n> and then libified so they can be used by git without spawning another\n> process.\n>\n> The first patch is not important to make the whole series work, but I\n> made this patch while working on it.\n>\n> This series keeps the commands `git merge-one-file', `git\n> merge-resolve', and `git merge-octopus', so any script depending on them\n> should keep working without any changes.\n\nWhile that may be true, with SKIP_DASHED_BUILT_INS=YesPlease, it is no\nlonger true. And that is a good thing!\n\nHowever, it also broke the CI build (`seen` sees a breakage in t9902.199).\n\nI will send out a fix shortly (see\nhttps://github.com/gitgitgadget/git/pull/745 for details).\n\nCiao,\nDscho\n\n>\n> This series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).  The\n> tip is tagged as \"rewrite-merge-strategies-v3\" at\n> https://github.com/agrn/git.\n>\n> Changes since v2:\n>\n>  - Enable `USE_THE_INDEX_COMPATIBILITY_MACROS' in merge-one-file.c and\n>    use read_cache() and hold_locked_index() instead of repo_read_index()\n>    and repo_hold_locked_index() to improve readability.\n>\n>  - Move file mode parsing to its own function in merge-one-file.c.\n>\n>  - Improve IO errors handling in do_merge_one_file().\n>\n>  - Return -1 instead of 1 when erroring out in do_merge_one_file() and\n>    merge_strategies_one_file().\n>\n>  - Use oid_to_hex_r() instead of oid_to_hex() in do_merge_one_file().\n>\n>  - Reformat multilines comments.\n>\n>  - Reworded a sentence in commit 3/11.\n>\n> Alban Gruin (11):\n>   t6027: modernise tests\n>   merge-one-file: rewrite in C\n>   merge-index: libify merge_one_path() and merge_all()\n>   merge-index: don't fork if the requested program is\n>     `git-merge-one-file'\n>   merge-resolve: rewrite in C\n>   merge-recursive: move better_branch_name() to merge.c\n>   merge-octopus: rewrite in C\n>   merge: use the \"resolve\" strategy without forking\n>   merge: use the \"octopus\" strategy without forking\n>   sequencer: use the \"resolve\" strategy without forking\n>   sequencer: use the \"octopus\" merge strategy without forking\n>\n>  Makefile                        |   7 +-\n>  builtin.h                       |   3 +\n>  builtin/merge-index.c           | 102 ++----\n>  builtin/merge-octopus.c         |  69 ++++\n>  builtin/merge-one-file.c        |  92 +++++\n>  builtin/merge-recursive.c       |  16 +-\n>  builtin/merge-resolve.c         |  69 ++++\n>  builtin/merge.c                 |   9 +-\n>  cache.h                         |   2 +-\n>  git-merge-octopus.sh            | 112 ------\n>  git-merge-one-file.sh           | 167 ---------\n>  git-merge-resolve.sh            |  54 ---\n>  git.c                           |   3 +\n>  merge-strategies.c              | 613 ++++++++++++++++++++++++++++++++\n>  merge-strategies.h              |  44 +++\n>  merge.c                         |  12 +\n>  sequencer.c                     |  16 +-\n>  t/t6407-merge-binary.sh         |  27 +-\n>  t/t6415-merge-dir-to-symlink.sh |   2 +-\n>  19 files changed, 972 insertions(+), 447 deletions(-)\n>  create mode 100644 builtin/merge-octopus.c\n>  create mode 100644 builtin/merge-one-file.c\n>  create mode 100644 builtin/merge-resolve.c\n>  delete mode 100755 git-merge-octopus.sh\n>  delete mode 100755 git-merge-one-file.sh\n>  delete mode 100755 git-merge-resolve.sh\n>  create mode 100644 merge-strategies.c\n>  create mode 100644 merge-strategies.h\n>\n> Range-diff against v2:\n>  1:  28c8fd11b6 =  1:  08c7df596a t6027: modernise tests\n>  2:  f5ab0fdf0a !  2:  ce911c99c0 merge-one-file: rewrite in C\n>     @@ builtin/merge-one-file.c (new)\n>      + * that might change the tree layout.\n>      + */\n>      +\n>     ++#define USE_THE_INDEX_COMPATIBILITY_MACROS\n>      +#include \"cache.h\"\n>      +#include \"builtin.h\"\n>      +#include \"lockfile.h\"\n>     @@ builtin/merge-one-file.c (new)\n>      +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n>      +\t\"Blob ids and modes should be empty for missing files.\";\n>      +\n>     ++static int read_mode(const char *name, const char *arg, unsigned int *mode)\n>     ++{\n>     ++\tchar *last;\n>     ++\tint ret = 0;\n>     ++\n>     ++\t*mode = strtol(arg, &last, 8);\n>     ++\n>     ++\tif (*last)\n>     ++\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n>     ++\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n>     ++\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n>     ++\n>     ++\treturn ret;\n>     ++}\n>     ++\n>      +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n>      +{\n>      +\tstruct object_id orig_blob, our_blob, their_blob,\n>     @@ builtin/merge-one-file.c (new)\n>      +\tif (argc != 8)\n>      +\t\tusage(builtin_merge_one_file_usage);\n>      +\n>     -+\tif (repo_read_index(the_repository) < 0)\n>     ++\tif (read_cache() < 0)\n>      +\t\tdie(\"invalid index\");\n>      +\n>     -+\trepo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);\n>     ++\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n>      +\n>      +\tif (!get_oid(argv[1], &orig_blob)) {\n>      +\t\tp_orig_blob = &orig_blob;\n>     -+\t\torig_mode = strtol(argv[5], NULL, 8);\n>     -+\n>     -+\t\tif (!(S_ISREG(orig_mode) || S_ISDIR(orig_mode) || S_ISLNK(orig_mode)))\n>     -+\t\t\tret |= error(_(\"invalid 'orig' mode: %o\"), orig_mode);\n>     ++\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n>      +\t}\n>      +\n>      +\tif (!get_oid(argv[2], &our_blob)) {\n>      +\t\tp_our_blob = &our_blob;\n>     -+\t\tour_mode = strtol(argv[6], NULL, 8);\n>     -+\n>     -+\t\tif (!(S_ISREG(our_mode) || S_ISDIR(our_mode) || S_ISLNK(our_mode)))\n>     -+\t\t\tret |= error(_(\"invalid 'our' mode: %o\"), our_mode);\n>     ++\t\tret = read_mode(\"our\", argv[6], &our_mode);\n>      +\t}\n>      +\n>      +\tif (!get_oid(argv[3], &their_blob)) {\n>      +\t\tp_their_blob = &their_blob;\n>     -+\t\ttheir_mode = strtol(argv[7], NULL, 8);\n>     -+\n>     -+\t\tif (!(S_ISREG(their_mode) || S_ISDIR(their_mode) || S_ISLNK(their_mode)))\n>     -+\t\t\tret = error(_(\"invalid 'their' mode: %o\"), their_mode);\n>     ++\t\tret = read_mode(\"their\", argv[7], &their_mode);\n>      +\t}\n>      +\n>      +\tif (ret)\n>     @@ builtin/merge-one-file.c (new)\n>      +\n>      +\tif (ret) {\n>      +\t\trollback_lock_file(&lock);\n>     -+\t\treturn ret;\n>     ++\t\treturn !!ret;\n>      +\t}\n>      +\n>     -+\treturn write_locked_index(the_repository->index, &lock, COMMIT_LOCK);\n>     ++\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>      +}\n>\n>       ## git-merge-one-file.sh (deleted) ##\n>     @@ merge-strategies.c (new)\n>      +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n>      +{\n>      +\tint ret, i, dest;\n>     ++\tssize_t written;\n>      +\tmmbuffer_t result = {NULL, 0};\n>      +\tmmfile_t mmfs[3];\n>      +\tstruct ll_merge_options merge_opts = {0};\n>     @@ merge-strategies.c (new)\n>      +\tfor (i = 0; i < 3; i++)\n>      +\t\tfree(mmfs[i].ptr);\n>      +\n>     -+\tif (ret > 127 || !orig_blob)\n>     -+\t\tret = error(_(\"content conflict in %s\"), path);\n>     ++\tif (ret < 0) {\n>     ++\t\tfree(result.ptr);\n>     ++\t\treturn error(_(\"Failed to execute internal merge\"));\n>     ++\t}\n>      +\n>     -+\t/* Create the working tree file, using \"our tree\" version from\n>     -+\t   the index, and then store the result of the merge. */\n>     ++\t/*\n>     ++\t * Create the working tree file, using \"our tree\" version from\n>     ++\t * the index, and then store the result of the merge.\n>     ++\t */\n>      +\tce = index_file_exists(istate, path, strlen(path), 0);\n>      +\tif (!ce)\n>      +\t\tBUG(\"file is not present in the cache?\");\n>      +\n>      +\tunlink(path);\n>     -+\tdest = open(path, O_WRONLY | O_CREAT, ce->ce_mode);\n>     -+\twrite_in_full(dest, result.ptr, result.size);\n>     ++\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n>     ++\t\tfree(result.ptr);\n>     ++\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n>     ++\t}\n>     ++\n>     ++\twritten = write_in_full(dest, result.ptr, result.size);\n>      +\tclose(dest);\n>      +\n>      +\tfree(result.ptr);\n>      +\n>     -+\tif (ret && our_mode != their_mode)\n>     ++\tif (written < 0)\n>     ++\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n>     ++\n>     ++\tif (ret != 0 || !orig_blob)\n>     ++\t\tret = error(_(\"content conflict in %s\"), path);\n>     ++\tif (our_mode != their_mode)\n>      +\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n>      +\t\t\t     orig_mode, our_mode, their_mode, path);\n>      +\tif (ret)\n>     -+\t\treturn 1;\n>     ++\t\treturn -1;\n>      +\n>      +\treturn add_file_to_index(istate, path, 0);\n>      +}\n>     @@ merge-strategies.c (new)\n>      +\tif (orig_blob &&\n>      +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n>      +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n>     -+\t\t/* Deleted in both or deleted in one and unchanged in\n>     -+\t\t   the other */\n>     ++\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n>      +\t\treturn merge_one_file_deleted(r->index,\n>      +\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n>      +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n>      +\telse if (!orig_blob && our_blob && !their_blob) {\n>     -+\t\t/* Added in one.  The other side did not add and we\n>     -+\t\t   added so there is nothing to be done, except making\n>     -+\t\t   the path merged. */\n>     ++\t\t/*\n>     ++\t\t * Added in one.  The other side did not add and we\n>     ++\t\t * added so there is nothing to be done, except making\n>     ++\t\t * the path merged.\n>     ++\t\t */\n>      +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n>      +\t} else if (!orig_blob && !our_blob && their_blob) {\n>      +\t\tprintf(_(\"Adding %s\\n\"), path);\n>     @@ merge-strategies.c (new)\n>      +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n>      +\n>      +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n>     -+\t\t\treturn 1;\n>     ++\t\t\treturn -1;\n>      +\t\treturn checkout_from_index(r->index, path);\n>      +\t} else if (!orig_blob && our_blob && their_blob &&\n>      +\t\t   oideq(our_blob, their_blob)) {\n>     -+\t\t/* Added in both, identically (check for same\n>     -+\t\t   permissions). */\n>     ++\t\t/* Added in both, identically (check for same permissions). */\n>      +\t\tif (our_mode != their_mode)\n>      +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n>      +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n>     @@ merge-strategies.c (new)\n>      +\t\tprintf(_(\"Adding %s\\n\"), path);\n>      +\n>      +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n>     -+\t\t\treturn 1;\n>     ++\t\t\treturn -1;\n>      +\t\treturn checkout_from_index(r->index, path);\n>      +\t} else if (our_blob && their_blob)\n>      +\t\t/* Modified in both, but differently. */\n>     @@ merge-strategies.c (new)\n>      +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n>      +\t\t\t\t\t orig_mode, our_mode, their_mode);\n>      +\telse {\n>     -+\t\tchar *orig_hex = \"\", *our_hex = \"\", *their_hex = \"\";\n>     ++\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n>     ++\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n>      +\n>      +\t\tif (orig_blob)\n>     -+\t\t\torig_hex = oid_to_hex(orig_blob);\n>     ++\t\t\toid_to_hex_r(orig_hex, orig_blob);\n>      +\t\tif (our_blob)\n>     -+\t\t\tour_hex = oid_to_hex(our_blob);\n>     ++\t\t\toid_to_hex_r(our_hex, our_blob);\n>      +\t\tif (their_blob)\n>     -+\t\t\ttheir_hex = oid_to_hex(their_blob);\n>     ++\t\t\toid_to_hex_r(their_hex, their_blob);\n>      +\n>      +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n>      +\t\t\t     path, orig_hex, our_hex, their_hex);\n>  3:  7f3ce7da17 !  3:  7f0999f5a3 merge-index: libify merge_one_path() and merge_all()\n>     @@ Commit message\n>\n>          To avoid this, this moves merge_one_path(), merge_all(), and their\n>          helpers to merge-strategies.c.  They also take a callback to dictate\n>     -    what they should do for each file.  For now, only one launching a new\n>     -    process is defined to preserve the behaviour of the builtin version.\n>     +    what they should do for each file.  For now, to preserve the behaviour\n>     +    of `merge-index', only one callback, launching a new process, is\n>     +    defined.\n>\n>          Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n>\n>  4:  07e6a6aaef =  4:  c0bc05406d merge-index: don't fork if the requested program is `git-merge-one-file'\n>  5:  117d4fc840 =  5:  cbfe192982 merge-resolve: rewrite in C\n>  6:  4fc955962b =  6:  35e386f626 merge-recursive: move better_branch_name() to merge.c\n>  7:  e7b9e15b34 !  7:  41eb0f7199 merge-octopus: rewrite in C\n>     @@ Makefile: BUILTIN_OBJS += builtin/mailsplit.o\n>       BUILTIN_OBJS += builtin/merge-recursive.o\n>\n>       ## builtin.h ##\n>     -@@ builtin.h: int cmd_mailsplit(int argc, const char **argv, const char *prefix);\n>     +@@ builtin.h: int cmd_maintenance(int argc, const char **argv, const char *prefix);\n>       int cmd_merge(int argc, const char **argv, const char *prefix);\n>       int cmd_merge_base(int argc, const char **argv, const char *prefix);\n>       int cmd_merge_index(int argc, const char **argv, const char *prefix);\n>     @@ builtin/merge-octopus.c (new)\n>      +\tif (repo_read_index(the_repository) < 0)\n>      +\t\tdie(\"corrupted cache\");\n>      +\n>     -+\t/* The first parameters up to -- are merge bases; the rest are\n>     -+\t * heads. */\n>     ++\t/*\n>     ++\t * The first parameters up to -- are merge bases; the rest are\n>     ++\t * heads.\n>     ++\t */\n>      +\tfor (i = 1; i < argc; i++) {\n>      +\t\tif (strcmp(argv[i], \"--\") == 0)\n>      +\t\t\tsep_seen = 1;\n>     @@ builtin/merge-octopus.c (new)\n>      +\t\t}\n>      +\t}\n>      +\n>     -+\t/* Reject if this is not an octopus -- resolve should be used\n>     -+\t * instead. */\n>     ++\t/*\n>     ++\t * Reject if this is not an octopus -- resolve should be used\n>     ++\t * instead.\n>     ++\t */\n>      +\tif (commit_list_count(remotes) < 2)\n>      +\t\treturn 2;\n>      +\n>     @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n>      +\t\tint can_ff = 1;\n>      +\n>      +\t\tif (ret) {\n>     -+\t\t\t/* We allow only last one to have a\n>     -+\t\t\t   hand-resolvable conflicts.  Last round failed\n>     -+\t\t\t   and we still had a head to merge. */\n>     ++\t\t\t/*\n>     ++\t\t\t * We allow only last one to have a\n>     ++\t\t\t * hand-resolvable conflicts.  Last round failed\n>     ++\t\t\t * and we still had a head to merge.\n>     ++\t\t\t */\n>      +\t\t\tputs(_(\"Automated merge did not work.\"));\n>      +\t\t\tputs(_(\"Should not be doing an octopus.\"));\n>      +\n>     @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n>      +\t\t}\n>      +\n>      +\t\tif (!non_ff_merge && can_ff) {\n>     -+\t\t\t/* The first head being merged was a\n>     -+\t\t\t   fast-forward.  Advance the reference commit\n>     -+\t\t\t   to the head being merged, and use that tree\n>     -+\t\t\t   as the intermediate result of the merge.  We\n>     -+\t\t\t   still need to count this as part of the\n>     -+\t\t\t   parent set. */\n>     ++\t\t\t/*\n>     ++\t\t\t * The first head being merged was a\n>     ++\t\t\t * fast-forward.  Advance the reference commit\n>     ++\t\t\t * to the head being merged, and use that tree\n>     ++\t\t\t * as the intermediate result of the merge.  We\n>     ++\t\t\t * still need to count this as part of the\n>     ++\t\t\t * parent set.\n>     ++\t\t\t */\n>      +\t\t\tstruct object_id oids[2];\n>      +\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n>      +\n>  8:  cd0662201d =  8:  8f6c1ac057 merge: use the \"resolve\" strategy without forking\n>  9:  0525ff0183 =  9:  b1125261d1 merge: use the \"octopus\" strategy without forking\n> 10:  6fbf599ba4 = 10:  8d0932fd02 sequencer: use the \"resolve\" strategy without forking\n> 11:  2c2dc3cc62 = 11:  e304723957 sequencer: use the \"octopus\" merge strategy without forking\n> --\n> 2.28.0.662.ge304723957\n>\n>\n>\n"},{"id":"407195","messageId":"xmqqh7r4uhrn.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201005122646.27994-4-alban.gruin@gmail.com","subject":"Re: [PATCH v3 03/11] merge-index: libify merge_one_path() and merge_all()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-09T04:48:12Z","receivedAt":"2020-10-09T04:49:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index bbe6f48698..f0e30f5624 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -2,6 +2,7 @@\n>  #include \"dir.h\"\n>  #include \"ll-merge.h\"\n>  #include \"merge-strategies.h\"\n> +#include \"run-command.h\"\n>  #include \"xdiff-interface.h\"\n>  \n>  static int add_to_index_cacheinfo(struct index_state *istate,\n> @@ -212,3 +213,101 @@ int merge_strategies_one_file(struct repository *r,\n>  \n>  \treturn 0;\n>  }\n> +\n> +int merge_program_cb(const struct object_id *orig_blob,\n> +\t\t     const struct object_id *our_blob,\n> +\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t     void *data)\n> +{\n> +\tchar ownbuf[3][GIT_MAX_HEXSZ] = {{0}};\n> +\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n> +\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n> +\t\t\t\t    NULL };\n> +\n> +\tif (orig_blob)\n> +\t\targuments[1] = oid_to_hex(orig_blob);\n> +\tif (our_blob)\n> +\t\targuments[2] = oid_to_hex(our_blob);\n> +\tif (their_blob)\n> +\t\targuments[3] = oid_to_hex(their_blob);\n\noid_to_hex() uses 4-slot rotating buffer, no?  Relying on the fact\nthat three would be available here without getting reused (or,\nrather, our caller didn't make its own calls and/or does not mind\nus invalidating all but one slot for them) feels a bit iffy.\n\nExtending ownbuf[] to 6 elements and using oid_to_hex_r() would be a\ntrivial way to clarify the code.\n\n> +\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n> +\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n> +\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n\nAnd these mode bits would not need GIT_MAX_HEXSZ to begin with.\nThis smells like a WIP that hasn't been carefullly proofread.\n\n\tchar oidbuf[3][GIT_MAX_HEXSZ] = { 0 };\n\tchar modebuf[3][8] = { 0 };\n\tchar *args[] = {\n\t\tdata, oidbuf[0], oidbuf[1], oidbuf[2], path,\n\t\tmodebuf[0], modebuf[1], modebuf[2], NULL,\n\t};\n        \n        if (orig_blob)\n\t\toid_to_hex_r(oidbuf[0], orig_blob);\n\t...\n\txsnprintf(modebuf[0], ...);\n\n\nEh, wait.  Is this meant to be able to drive \"git-merge-one-file\",\ni.e. a missing common/ours/theirs is indicated by an empty string\nin both oiod and mode?  If so, an unconditional xsnprintf() would\neither give garbage or \"0\" at best, neither of which is an empty\nstring.  So the body would be more like\n\n\tif (orig_blob) {\n\t\toid_to_hex_r(oidbuf[0], orig_blob);\n\t\txsnprintf(modebuf[0], \"%o\", orig_mode);\n\t}\n\tif (our_blob) {\n\t\toid_to_hex_r(oidbuf[1], our_blob);\n\t\txsnprintf(modebuf[1], \"%o\", our_mode);\n\t}\n\t...\n\nwouldn't it?\n\n> +\treturn run_command_v_opt(arguments, 0);\n> +}\n> +\n> +static int merge_entry(struct index_state *istate, int quiet, int pos,\n> +\t\t       const char *path, merge_cb cb, void *data)\n\nWhen we use an identifier \"cb\", it typically means callback data,\nnot a callback function which is often called \"fn\".  So, name the\ntype \"merge_fn\" (or \"merge_func\"), and call the parameter \"fn\".\n\n> +{\n> +\tint found = 0;\n> +\tconst struct object_id *oids[3] = {NULL};\n> +\tunsigned int modes[3] = {0};\n> +\n> +\tdo {\n> +\t\tconst struct cache_entry *ce = istate->cache[pos];\n> +\t\tint stage = ce_stage(ce);\n> +\n> +\t\tif (strcmp(ce->name, path))\n> +\t\t\tbreak;\n> +\t\tfound++;\n> +\t\toids[stage - 1] = &ce->oid;\n> +\t\tmodes[stage - 1] = ce->ce_mode;\n> +\t} while (++pos < istate->cache_nr);\n> +\tif (!found)\n> +\t\treturn error(_(\"%s is not in the cache\"), path);\n> +\n> +\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n> +\t\tif (!quiet)\n> +\t\t\terror(_(\"Merge program failed\"));\n> +\t\treturn -2;\n> +\t}\n> +\n> +\treturn found;\n> +}\n\nThis copies from builtin/merge-index.c::merge_entry().\n\n> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n> +\t\t   const char *path, merge_cb cb, void *data)\n> +{\n> +\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n> +\n> +\t/*\n> +\t * If it already exists in the cache as stage0, it's\n> +\t * already merged and there is nothing to do.\n> +\t */\n> +\tif (pos < 0) {\n> +\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n> +\t\tif (ret == -1)\n> +\t\t\treturn -1;\n> +\t\telse if (ret == -2)\n> +\t\t\treturn 1;\n> +\t}\n> +\treturn 0;\n> +}\n\nLikewise from the same function in that file.\n\nAre we removing the \"git merge-index\" program?  Reusing the same\nidentifier for these copied-and-pasted pairs of functions bothers\nme for two reasons.\n\n - An indentifier that was clear and unique enough in the original\n   context as a file-scope static may not be a good name as a global\n   identifier.  \n\n - Having two similar-looking functions with the same name makes\n   reading and learning the codebase starting at \"git grep\" hits\n   more difficult than necessary.\n\n> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n> +\t      merge_cb cb, void *data)\n> +{\n> +\tint err = 0, i, ret;\n> +\tfor (i = 0; i < istate->cache_nr; i++) {\n> +\t\tconst struct cache_entry *ce = istate->cache[i];\n> +\t\tif (!ce_stage(ce))\n> +\t\t\tcontinue;\n> +\n> +\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n> +\t\tif (ret > 0)\n> +\t\t\ti += ret - 1;\n> +\t\telse if (ret == -1)\n> +\t\t\treturn -1;\n> +\t\telse if (ret == -2) {\n> +\t\t\tif (oneshot)\n> +\t\t\t\terr++;\n> +\t\t\telse\n> +\t\t\t\treturn 1;\n> +\t\t}\n> +\t}\n> +\n> +\treturn err;\n> +}\n\nLikewise.\n\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> index b527d145c7..cf78d7eaf4 100644\n> --- a/merge-strategies.h\n> +++ b/merge-strategies.h\n> @@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n>  \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>  \t\t\t      unsigned int their_mode);\n>  \n> +typedef int (*merge_cb)(const struct object_id *orig_blob,\n> +\t\t\tconst struct object_id *our_blob,\n> +\t\t\tconst struct object_id *their_blob, const char *path,\n> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\tvoid *data);\n\nCall it \"merge_one_file_func\", probably.\n\n> +int merge_program_cb(const struct object_id *orig_blob,\n\nCall it spawn_merge_one_file() perhaps?\n\n> +\t\t     const struct object_id *our_blob,\n> +\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t     void *data);\n> +\n> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n> +\t\t   const char *path, merge_cb cb, void *data);\n> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n> +\t      merge_cb cb, void *data);\n>  #endif /* MERGE_STRATEGIES_H */\n"},{"id":"407738","messageId":"xmqqv9faat2i.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201005122646.27994-5-alban.gruin@gmail.com","subject":"Re: [PATCH v3 04/11] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-16T19:07:01Z","receivedAt":"2020-10-16T19:07:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> Since `git-merge-one-file' has been rewritten and libified, this teaches\n> `merge-index' to call merge_strategies_one_file() without forking using\n> a new callback, merge_one_file_cb().\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n\nI do not know how much of the change in this patch survives when the\nprevious step gets adjusted, so I'll skip this step for now.\n\nThanks.\n"},{"id":"407739","messageId":"xmqqr1pyashe.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201005122646.27994-6-alban.gruin@gmail.com","subject":"Re: [PATCH v3 05/11] merge-resolve: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-16T19:19:41Z","receivedAt":"2020-10-16T19:19:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_resolve_usage[] =\n> +\t\"git merge-resolve <bases>... -- <head> <remote>\";\n> +\n> +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint i, is_baseless = 1, sep_seen = 0;\n> +\tconst char *head = NULL;\n> +\tstruct commit_list *bases = NULL, *remote = NULL;\n> +\tstruct commit_list **next_base = &bases;\n> +\n> +\tif (argc < 5)\n> +\t\tusage(builtin_merge_resolve_usage);\n> +\n> +\tsetup_work_tree();\n> +\tif (repo_read_index(the_repository) < 0)\n> +\t\tdie(\"invalid index\");\n> +\n> +\t/* The first parameters up to -- are merge bases; the rest are\n> +\t * heads. */\n\nStyle (I won't repeat).\n\n> +\tfor (i = 1; i < argc; i++) {\n> +\t\tif (strcmp(argv[i], \"--\") == 0)\n\n\tif (!strcmp(...))\n\nis more typical than comparing with \"== 0\".\n\n> +\t\t\tsep_seen = 1;\n> +\t\telse if (strcmp(argv[i], \"-h\") == 0)\n> +\t\t\tusage(builtin_merge_resolve_usage);\n> +\t\telse if (sep_seen && !head)\n> +\t\t\thead = argv[i];\n> +\t\telse if (remote) {\n> +\t\t\t/* Give up if we are given two or more remotes.\n> +\t\t\t * Not handling octopus. */\n> +\t\t\treturn 2;\n> +\t\t} else {\n> +\t\t\tstruct object_id oid;\n> +\n> +\t\t\tget_oid(argv[i], &oid);\n> +\t\t\tis_baseless &= sep_seen;\n> +\n> +\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n\nWhat is this business about an empty tree about?\n\n> +\t\t\t\tstruct commit *commit;\n> +\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n> +\n> +\t\t\t\tif (sep_seen)\n> +\t\t\t\t\tcommit_list_append(commit, &remote);\n> +\t\t\t\telse\n> +\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n> +\t\t\t}\n> +\t\t}\n> +\t}\n> +\n> +\t/* Give up if this is a baseless merge. */\n> +\tif (is_baseless)\n> +\t\treturn 2;\n\nThis is quite convoluted.  \n\nThe original is much more straight-forward.  We just said \"grab\neverything before we see '--' and call them bases; immediately after\n'--' is HEAD and everything else is remote.  Now do we have any\nbase?  Otherwise we cannot handle it\".\n\nI cannot see an equivalence to it in the rewritten result, with the\nbit operation with is_baseless and sep_seen.  Wouldn't it be the\nmatter of checking if next_base is NULL, or is there something more\nsubtle that deserves in-code comment going on?\n\nThanks.\n"},{"id":"408088","messageId":"e407ce78-8f93-3fb1-4ef2-ce8213f39df2@gmail.com","threadId":"53755","inReplyTo":"xmqqmu0z3tge.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 02/11] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-10-21T19:47:39Z","receivedAt":"2020-10-21T19:48:00Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nOn 07/10/2020 00:01, Junio C Hamano wrote:\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> This rewrites `git merge-one-file' from shell to C.  This port is not\n>> completely straightforward: to save precious cycles by avoiding reading\n>> and flushing the index repeatedly, write temporary files when an\n>> operation can be performed in-memory, or allow other function to use the\n>> rewrite without forking nor worrying about the index,...\n> \n> So, the in-core index is still used, but when the contents of the in-core\n> index does not have to be written out disk, we just don't?  Makes sense.\n> \n\n>> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n>> new file mode 100644\n>> index 0000000000..598338ba16\n>> --- /dev/null\n>> +++ b/builtin/merge-one-file.c\n>> @@ -0,0 +1,92 @@\n>> +/*\n>> + * Builtin \"git merge-one-file\"\n>> + *\n>> + * Copyright (c) 2020 Alban Gruin\n>> + *\n>> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n>> + *\n>> + * This is the git per-file merge utility, called with\n>> + *\n>> + *   argv[1] - original file SHA1 (or empty)\n>> + *   argv[2] - file in branch1 SHA1 (or empty)\n>> + *   argv[3] - file in branch2 SHA1 (or empty)\n> \n> Let's modernize this comment while we are at it.\n> \n>     SHA1 -> \"object name\" (or \"blob object name\")\n> \n>> + *   argv[4] - pathname in repository\n>> + *   argv[5] - original file mode (or empty)\n>> + *   argv[6] - file in branch1 mode (or empty)\n>> + *   argv[7] - file in branch2 mode (or empty)\n>> + *\n>> + * Handle some trivial cases. The _really_ trivial cases have been\n>> + * handled already by git read-tree, but that one doesn't do any merges\n>> + * that might change the tree layout.\n>> + */\n>> +\n>> +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n>> +#include \"cache.h\"\n>> +#include \"builtin.h\"\n>> +#include \"lockfile.h\"\n>> +#include \"merge-strategies.h\"\n>> +\n>> +static const char builtin_merge_one_file_usage[] =\n>> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n>> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n>> +\t\"Blob ids and modes should be empty for missing files.\";\n>> +\n>> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n>> +{\n>> +\tchar *last;\n>> +\tint ret = 0;\n>> +\n>> +\t*mode = strtol(arg, &last, 8);\n>> +\n>> +\tif (*last)\n>> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n>> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n>> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n>> +\n>> +\treturn ret;\n>> +}\n>> +\n>> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n>> +{\n>> +\tstruct object_id orig_blob, our_blob, their_blob,\n>> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n>> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n>> +\tstruct lock_file lock = LOCK_INIT;\n>> +\n>> +\tif (argc != 8)\n>> +\t\tusage(builtin_merge_one_file_usage);\n>> +\n>> +\tif (read_cache() < 0)\n>> +\t\tdie(\"invalid index\");\n>> +\n>> +\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n>> +\n>> +\tif (!get_oid(argv[1], &orig_blob)) {\n>> +\t\tp_orig_blob = &orig_blob;\n>> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n>> +\t}\n> \n> argv[1] is defined as \"either the object name of the blob in the\n> common ancestor, or an empty string\".  So you need to distinguish\n> three cases here, but you are only catching two.\n> \n>  - argv[1] is an empty string; p_orig_blob can legitimately be left\n>    NULL.\n> \n>  - argv[1] is a valid blob object name.  orig_blob should be\n>    populated and p_orig_blob should point at it.\n> \n>  - argv[1] is garbage, names a non-blob object, or there is no such\n>    object with that name.  Don't we want to catch it as a mistake?\n> \n> Also, when argv[1] is an empty string, argv[5] must also be an empty\n> string, or we got a wrong input---don't we want to catch it as a\n> mistake?\n> \n> The third case needs a bit of thought.  For example, if $1 and $2\n> are the same and points at a non-existent object, we know we won't\n> care because we only care about $3.  In a lazily-cloned repository,\n> that may matter---we would not want to fail even if we not have blob\n> $1 and $2, as long as they are reasonably spelled a full hexadecimal\n> object name.  But we would want to fail if blob object named by $3\n> is missing.\n> \n> One way to achieve semantics closer to the above than the posted\n> patch may be to tighten the parsing.  Instead of using \"anything\n> goes\" get_oid(), use get_oid_hex(), perhaps.\n> \n>> +\tif (!get_oid(argv[2], &our_blob)) {\n>> +\t\tp_our_blob = &our_blob;\n>> +\t\tret = read_mode(\"our\", argv[6], &our_mode);\n>> +\t}\n>> +\n>> +\tif (!get_oid(argv[3], &their_blob)) {\n>> +\t\tp_their_blob = &their_blob;\n>> +\t\tret = read_mode(\"their\", argv[7], &their_mode);\n>> +\t}\n>> +\n>> +\tif (ret)\n>> +\t\treturn ret;\n>> +\n>> +\tret = merge_strategies_one_file(the_repository,\n>> +\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n>> +\t\t\t\t\torig_mode, our_mode, their_mode);\n> \n> That's a funny function name.  It's not like the function will be\n> taught different strategy to handle the three-way merge, no?  It\n> probably makes sense to name it after what it does, which is \"three\n> way merge\".\n> \n\nOkay.  There's already a function called threeway_merge() in\nunpack_trees() that does something different.\nmerge_strategies_threeway() should be good?\n\n>> +\tif (ret) {\n>> +\t\trollback_lock_file(&lock);\n>> +\t\treturn !!ret;\n>> +\t}\n>> +\n>> +\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>> +}\n> \n>> diff --git a/merge-strategies.c b/merge-strategies.c\n>> new file mode 100644\n>> index 0000000000..bbe6f48698\n>> --- /dev/null\n>> +++ b/merge-strategies.c\n>> @@ -0,0 +1,214 @@\n>> +#include \"cache.h\"\n>> +#include \"dir.h\"\n>> +#include \"ll-merge.h\"\n>> +#include \"merge-strategies.h\"\n>> +#include \"xdiff-interface.h\"\n>> +\n> \n>> +static int add_to_index_cacheinfo(struct index_state *istate,\n>> +\t\t\t\t  unsigned int mode,\n>> +\t\t\t\t  const struct object_id *oid, const char *path)\n>> +{\n>> +\tstruct cache_entry *ce;\n>> +\tint len, option;\n>> +\n>> +\tif (!verify_path(path, mode))\n>> +\t\treturn error(_(\"Invalid path '%s'\"), path);\n>> +\n>> +\tlen = strlen(path);\n>> +\tce = make_empty_cache_entry(istate, len);\n>> +\n>> +\toidcpy(&ce->oid, oid);\n>> +\tmemcpy(ce->name, path, len);\n>> +\tce->ce_flags = create_ce_flags(0);\n>> +\tce->ce_namelen = len;\n>> +\tce->ce_mode = create_ce_mode(mode);\n>> +\tif (assume_unchanged)\n>> +\t\tce->ce_flags |= CE_VALID;\n>> +\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n>> +\tif (add_index_entry(istate, ce, option))\n>> +\t\treturn error(_(\"%s: cannot add to the index\"), path);\n>> +\n>> +\treturn 0;\n>> +}\n> \n> The above correctly does 'git update-index --add --cacheinfo \"$6\"\n> \"$2\" \"$4\"' but don't copy-and-paste existing code to do so.  Add one\n> preliminary patch before everything else in the series to massage\n> and extract add_cacheinfo() function out of builtin/update-index.c,\n> move it to somewhere common like read-cache.c and so that we can\n> call it from here.\n> \n\nHmm, I’d really like to do this, but I have one remark/question about\nit.  In builtin/update-index.c, when add_cache_entry() fails, this\nmessage is printed:\n\n\tcannot add to the index - missing --add option?\n\nObviously, this is not what we want to show in git-merge when\nadd_index_entry() fails.  But then, verify_path() can also fail, and\nwill show a sensible message for any situation:\n\n\tInvalid path '%s'\n\nShould I return error when verify_path() fails, but eg. -2 in the case\nof add_index_entry(), and if this new add_cacheinfo() returns -2 but not\n-1, print the correct message?  Or let the caller verify the path so it\ncannot fail because of this?\n\n>> +static int checkout_from_index(struct index_state *istate, const char *path)\n>> +{\n>> +\tstruct checkout state = CHECKOUT_INIT;\n>> +\tstruct cache_entry *ce;\n>> +\n>> +\tstate.istate = istate;\n>> +\tstate.force = 1;\n>> +\tstate.base_dir = \"\";\n>> +\tstate.base_dir_len = 0;\n>> +\n>> +\tce = index_file_exists(istate, path, strlen(path), 0);\n> \n> This call is unfortunate for the reasons I mention later.\n> \n> But if you must have this call, then you need to sanity check what\n> you get from index_file_exists().  ce must be a merged cache entry,\n> so\n> \n> \tif (!ce || ce_stage(ce))\n> \t\tBUG(...);\n> \n\nThat’s ok, I managed to remove it following your advice.\n\n>> +\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n>> +\t\treturn error(_(\"%s: cannot checkout file\"), path);\n>> +\treturn 0;\n>> +}\n>> +\n>> +static int merge_one_file_deleted(struct index_state *istate,\n>> +\t\t\t\t  const struct object_id *orig_blob,\n>> +\t\t\t\t  const struct object_id *our_blob,\n>> +\t\t\t\t  const struct object_id *their_blob, const char *path,\n>> +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n>> +{\n>> +\tif ((our_blob && orig_mode != our_mode) ||\n>> +\t    (their_blob && orig_mode != their_mode))\n>> +\t\treturn error(_(\"File %s deleted on one branch but had its \"\n>> +\t\t\t       \"permissions changed on the other.\"), path);\n>> +\n>> +\tif (our_blob) {\n>> +\t\tprintf(_(\"Removing %s\\n\"), path);\n>> +\n>> +\t\tif (file_exists(path))\n>> +\t\t\tremove_path(path);\n>> +\t}\n>> +\n>> +\tif (remove_file_from_index(istate, path))\n>> +\t\treturn error(\"%s: cannot remove from the index\", path);\n>> +\treturn 0;\n> \n> If the side that did not remove changed the mode, we don't silently\n> remove but fail and give a chance to inspect the situation to the\n> end user.  If we had the blob and it is removed by them, we give a\n> message and only in that case we remove the file from the working\n> tree, together with any leading directory that has become empty.\n> \n> And after that we make sure that the path is no longer in the\n> index.  The function removes entries for the path at all the stages,\n> which is exactly what we want.\n> \n> OK.\n> \n>> +}\n>> +\n>> +static int do_merge_one_file(struct index_state *istate,\n>> +\t\t\t     const struct object_id *orig_blob,\n>> +\t\t\t     const struct object_id *our_blob,\n>> +\t\t\t     const struct object_id *their_blob, const char *path,\n>> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n>> +{\n>> +\tint ret, i, dest;\n>> +\tssize_t written;\n>> +\tmmbuffer_t result = {NULL, 0};\n>> +\tmmfile_t mmfs[3];\n>> +\tstruct ll_merge_options merge_opts = {0};\n>> +\tstruct cache_entry *ce;\n>> +\n>> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n>> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n>> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n>> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n>> +\n>> +\tread_mmblob(mmfs + 1, our_blob);\n>> +\tread_mmblob(mmfs + 2, their_blob);\n>> +\n>> +\tif (orig_blob) {\n>> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n>> +\t\tread_mmblob(mmfs + 0, orig_blob);\n>> +\t} else {\n>> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n>> +\t\tread_mmblob(mmfs + 0, &null_oid);\n>> +\t}\n>> +\n>> +\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n>> +\tret = ll_merge(&result, path,\n>> +\t\t       mmfs + 0, \"orig\",\n>> +\t\t       mmfs + 1, \"our\",\n>> +\t\t       mmfs + 2, \"their\",\n>> +\t\t       istate, &merge_opts);\n> \n> Is it correct to call into ll_merge() here?  The original used to\n> call \"git merge-file\" which called into xdl_merge().  Calling into\n> ll_merge() means the path is used to look up the attributes and use\n> the custom merge driver, which I am not offhand sure is what we want\n> to see at this low level (and if it turns out to be a good idea, we\n> definitely should explain the change of semantics in the proposed\n> log message for this commit).\n> \n>> +\tfor (i = 0; i < 3; i++)\n>> +\t\tfree(mmfs[i].ptr);\n>> +\n>> +\tif (ret < 0) {\n>> +\t\tfree(result.ptr);\n>> +\t\treturn error(_(\"Failed to execute internal merge\"));\n>> +\t}\n>> +\n>> +\t/*\n>> +\t * Create the working tree file, using \"our tree\" version from\n>> +\t * the index, and then store the result of the merge.\n>> +\t */\n> \n> The above is copied from the original, to explain what it did after\n> the comment, but it does not seem to match what the new code does.\n> \n>> +\tce = index_file_exists(istate, path, strlen(path), 0);\n>> +\tif (!ce)\n>> +\t\tBUG(\"file is not present in the cache?\");\n>> +\n>> +\tunlink(path);\n>> +\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n>> +\t\tfree(result.ptr);\n>> +\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n>> +\t}\n>> +\n>> +\twritten = write_in_full(dest, result.ptr, result.size);\n>> +\tclose(dest);\n>> +\n>> +\tfree(result.ptr);\n>> +\n>> +\tif (written < 0)\n>> +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n>> +\n> \n> This open(..., ce->ce_mode) call is way insufficient.\n> \n> The comment we have above this part of the code talks about the\n> difficulty of doing this correctly in scripted version.  Creating a\n> file by 'git checkout-index -f --stage=2 -- \"$4\"' and reusing it to\n> store the merged contents was the cleanest and easiest way without\n> having direct access to adjust_shared_perm() to create a working\n> tree file with the correct permission bits.\n> \n> We are writing in C, so we should be able to do much better than the\n> scripted version, as we can later call adjust_shared_perm().\n> \n\nI'm not sure I understand the issue correctly.\n\nIs this because I fetch an entry from the index to have the mode of the\nfile, instead of using `our_mode'?  So I should move the error handling\nof ll_merge()/xdl_merge() and the detection of the permission conflict\nbefore writing in the file, and call open(…, our_mode)?\n\nI'm also not sure why we need adjust_shared_perm() here.\n\n>> +\tif (ret != 0 || !orig_blob)\n>> +\t\tret = error(_(\"content conflict in %s\"), path);\n>> +\tif (our_mode != their_mode)\n>> +\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n>> +\t\t\t     orig_mode, our_mode, their_mode, path);\n>> +\tif (ret)\n>> +\t\treturn -1;\n>> +\n>> +\treturn add_file_to_index(istate, path, 0);\n>> +}\n>> +\n>> +int merge_strategies_one_file(struct repository *r,\n>> +\t\t\t      const struct object_id *orig_blob,\n>> +\t\t\t      const struct object_id *our_blob,\n>> +\t\t\t      const struct object_id *their_blob, const char *path,\n>> +\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>> +\t\t\t      unsigned int their_mode)\n>> +{\n> \n> In a long if/else if/else if/.../else cascade, enclose all bodies in\n> braces, if any one of them has a multi-statement body, to avoid\n> being distracting.\n> \n>> +\tif (orig_blob &&\n>> +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n>> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n>> +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n>> +\t\treturn merge_one_file_deleted(r->index,\n>> +\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n>> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n> \n> OK, we've already reviewed that function.\n> \n>> +\telse if (!orig_blob && our_blob && !their_blob) {\n>> +\t\t/*\n>> +\t\t * Added in one.  The other side did not add and we\n>> +\t\t * added so there is nothing to be done, except making\n>> +\t\t * the path merged.\n>> +\t\t */\n>> +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n> \n> OK, we've already reviewed that function.\n> \n>> +\t} else if (!orig_blob && !our_blob && their_blob) {\n>> +\t\tprintf(_(\"Adding %s\\n\"), path);\n>> +\n>> +\t\tif (file_exists(path))\n>> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n>> +\n>> +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n>> +\t\t\treturn -1;\n>> +\t\treturn checkout_from_index(r->index, path);\n> \n> You did \"add_to_index_cacheinfo()\", so you MUST know which ce is to\n> be checked out.\n> \n> Consider if it is worth to teach add_to_index_cacheinfo() to give\n> you ce back and pass it to checkout_from_index(); that way, you do\n> not have to call index_file_exists() based on path in the function.\n> \n\nOK, this is doable.\n\n>> +\t} else if (!orig_blob && our_blob && their_blob &&\n>> +\t\t   oideq(our_blob, their_blob)) {\n>> +\t\t/* Added in both, identically (check for same permissions). */\n>> +\t\tif (our_mode != their_mode)\n>> +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n>> +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n>> +\t\t\t\t     path, our_mode, their_mode);\n>> +\n>> +\t\tprintf(_(\"Adding %s\\n\"), path);\n>> +\n>> +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n>> +\t\t\treturn -1;\n>> +\t\treturn checkout_from_index(r->index, path);\n> \n> Ditto.\n> \n>> +\t} else if (our_blob && their_blob)\n>> +\t\t/* Modified in both, but differently. */\n>> +\t\treturn do_merge_one_file(r->index,\n>> +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n>> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n>> +\telse {\n>> +\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n>> +\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n>> +\n>> +\t\tif (orig_blob)\n>> +\t\t\toid_to_hex_r(orig_hex, orig_blob);\n>> +\t\tif (our_blob)\n>> +\t\t\toid_to_hex_r(our_hex, our_blob);\n>> +\t\tif (their_blob)\n>> +\t\t\toid_to_hex_r(their_hex, their_blob);\n>> +\n>> +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n>> +\t\t\t     path, orig_hex, our_hex, their_hex);\n>> +\t}\n>> +\n>> +\treturn 0;\n>> +}\n> \n> I can see that this does go in the right direction.  With a bit more\n> attention to details it would soon be production-ready quality.\n> \n> Thanks.\n> \n\nThank you,\nAlban\n\n"},{"id":"408092","messageId":"xmqqimb3728g.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"e407ce78-8f93-3fb1-4ef2-ce8213f39df2@gmail.com","subject":"Re: [PATCH v3 02/11] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-21T20:28:31Z","receivedAt":"2020-10-21T20:28:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n>>> +\t/*\n>>> +\t * Create the working tree file, using \"our tree\" version from\n>>> +\t * the index, and then store the result of the merge.\n>>> +\t */\n>> \n>> The above is copied from the original, to explain what it did after\n>> the comment, but it does not seem to match what the new code does.\n>> \n>>> +\tce = index_file_exists(istate, path, strlen(path), 0);\n>>> +\tif (!ce)\n>>> +\t\tBUG(\"file is not present in the cache?\");\n>>> +\n>>> +\tunlink(path);\n>>> +\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n>>> +\t\tfree(result.ptr);\n>>> +\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n>>> +\t}\n>>> +\n>>> +\twritten = write_in_full(dest, result.ptr, result.size);\n>>> +\tclose(dest);\n>>> +\n>>> +\tfree(result.ptr);\n>>> +\n>>> +\tif (written < 0)\n>>> +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n>>> +\n>> \n>> This open(..., ce->ce_mode) call is way insufficient.\n>> \n>> The comment we have above this part of the code talks about the\n>> difficulty of doing this correctly in scripted version.  Creating a\n>> file by 'git checkout-index -f --stage=2 -- \"$4\"' and reusing it to\n>> store the merged contents was the cleanest and easiest way without\n>> having direct access to adjust_shared_perm() to create a working\n>> tree file with the correct permission bits.\n\nThe original that the comment applies to does this\n\n\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n\nto create path \"$4\" with the correct mode bits, instead of a naïve\n\n\tmv \"$src1\" \"$4\"\n\nbecause the filemode 'git checkout-index -f --stage=2 -- \"$4\"' gives\nto file \"$4\" is by definition the most correct one for the path.\nThe command knows how user's umask and type bits in the index should\ninteract and produce the final mode bits, but \"$src1\" was created\nwithout any regard to the mode bits---the 'git merge-file' command\nonly cares about the contents and not filemode.  We can even lose\nthe executable bit that way.  And preparing \"$4\" and then catting\nthe computed contents into it was a roundabout way (it wastes the\nentire writing-out of the contents from the index), and that was\nwhat the comment was about.\n\nBut all that is unnecessary once you port this to C.  So the comment\ndoes not apply to the code you wrote, I think, and should just be\ndropped.\n\n"},{"id":"408093","messageId":"xmqqeelr724u.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"e407ce78-8f93-3fb1-4ef2-ce8213f39df2@gmail.com","subject":"Re: [PATCH v3 02/11] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-21T20:30:41Z","receivedAt":"2020-10-21T20:30:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n>>> +\tint ret, i, dest;\n>>> +\tssize_t written;\n>>> +\tmmbuffer_t result = {NULL, 0};\n>>> +\tmmfile_t mmfs[3];\n>>> +\tstruct ll_merge_options merge_opts = {0};\n>>> +\tstruct cache_entry *ce;\n>>> +\n>>> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n>>> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n>>> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n>>> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n>>> +\n>>> +\tread_mmblob(mmfs + 1, our_blob);\n>>> +\tread_mmblob(mmfs + 2, their_blob);\n>>> +\n>>> +\tif (orig_blob) {\n>>> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n>>> +\t\tread_mmblob(mmfs + 0, orig_blob);\n>>> +\t} else {\n>>> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n>>> +\t\tread_mmblob(mmfs + 0, &null_oid);\n>>> +\t}\n>>> +\n>>> +\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n>>> +\tret = ll_merge(&result, path,\n>>> +\t\t       mmfs + 0, \"orig\",\n>>> +\t\t       mmfs + 1, \"our\",\n>>> +\t\t       mmfs + 2, \"their\",\n>>> +\t\t       istate, &merge_opts);\n>> \n>> Is it correct to call into ll_merge() here?  The original used to\n>> call \"git merge-file\" which called into xdl_merge().  Calling into\n>> ll_merge() means the path is used to look up the attributes and use\n>> the custom merge driver, which I am not offhand sure is what we want\n>> to see at this low level (and if it turns out to be a good idea, we\n>> definitely should explain the change of semantics in the proposed\n>> log message for this commit).\n\nI am still not sure if it is correct to call ll_merge() and not the\nxdl_merge() from here.  We need to highlight this change in the log\nmessage, if we were still going to do this.\n\nThanks.\n\n"},{"id":"408102","messageId":"xmqqsga75l9g.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"xmqqimb3728g.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 02/11] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-21T21:20:27Z","receivedAt":"2020-10-21T21:20:32Z","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>>> This open(..., ce->ce_mode) call is way insufficient.\n>>> ...\n> But all that is unnecessary once you port this to C.  So the comment\n> does not apply to the code you wrote, I think, and should just be\n> dropped.\n\nSorry, forgot to mention one thing.  Using ce->ce_mode to create the\noutput file is the way how helpers in entry.c check out paths from\nthe index to the working tree, so the code is OK.  It's just the\ncopied comment was about the issue that your code did not even have\nto worry about.\n\nThanks.\n"},{"id":"409292","messageId":"d6598312-00ba-9ec0-1f67-2b3977d7e308@gmail.com","threadId":"53755","inReplyTo":"xmqqh7r4uhrn.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 03/11] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-06T19:53:08Z","receivedAt":"2020-11-06T19:53:19Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nLe 09/10/2020 à 06:48, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> diff --git a/merge-strategies.c b/merge-strategies.c\n>> index bbe6f48698..f0e30f5624 100644\n>> --- a/merge-strategies.c\n>> +++ b/merge-strategies.c\n>> @@ -2,6 +2,7 @@\n>>  #include \"dir.h\"\n>>  #include \"ll-merge.h\"\n>>  #include \"merge-strategies.h\"\n>> +#include \"run-command.h\"\n>>  #include \"xdiff-interface.h\"\n>>  \n>>  static int add_to_index_cacheinfo(struct index_state *istate,\n>> @@ -212,3 +213,101 @@ int merge_strategies_one_file(struct repository *r,\n>>  \n>>  \treturn 0;\n>>  }\n>> +\n>> +int merge_program_cb(const struct object_id *orig_blob,\n>> +\t\t     const struct object_id *our_blob,\n>> +\t\t     const struct object_id *their_blob, const char *path,\n>> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t     void *data)\n>> +{\n>> +\tchar ownbuf[3][GIT_MAX_HEXSZ] = {{0}};\n>> +\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n>> +\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n>> +\t\t\t\t    NULL };\n>> +\n>> +\tif (orig_blob)\n>> +\t\targuments[1] = oid_to_hex(orig_blob);\n>> +\tif (our_blob)\n>> +\t\targuments[2] = oid_to_hex(our_blob);\n>> +\tif (their_blob)\n>> +\t\targuments[3] = oid_to_hex(their_blob);\n> \n> oid_to_hex() uses 4-slot rotating buffer, no?  Relying on the fact\n> that three would be available here without getting reused (or,\n> rather, our caller didn't make its own calls and/or does not mind\n> us invalidating all but one slot for them) feels a bit iffy.\n> \n> Extending ownbuf[] to 6 elements and using oid_to_hex_r() would be a\n> trivial way to clarify the code.\n> \n>> +\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n>> +\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n>> +\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n> \n> And these mode bits would not need GIT_MAX_HEXSZ to begin with.\n> This smells like a WIP that hasn't been carefullly proofread.\n> \n> \tchar oidbuf[3][GIT_MAX_HEXSZ] = { 0 };\n> \tchar modebuf[3][8] = { 0 };\n\nSo here I picked GIT_MAX_HEXSZ + 1 and 10 for those buffers, they are\nalready used by builtin/diff.c.\n\n> \tchar *args[] = {\n> \t\tdata, oidbuf[0], oidbuf[1], oidbuf[2], path,\n> \t\tmodebuf[0], modebuf[1], modebuf[2], NULL,\n> \t};\n>         \n>         if (orig_blob)\n> \t\toid_to_hex_r(oidbuf[0], orig_blob);\n> \t...\n> \txsnprintf(modebuf[0], ...);\n> \n> \n> Eh, wait.  Is this meant to be able to drive \"git-merge-one-file\",\n> i.e. a missing common/ours/theirs is indicated by an empty string\n> in both oiod and mode?  If so, an unconditional xsnprintf() would\n> either give garbage or \"0\" at best, neither of which is an empty\n> string.  So the body would be more like\n> \n> \tif (orig_blob) {\n> \t\toid_to_hex_r(oidbuf[0], orig_blob);\n> \t\txsnprintf(modebuf[0], \"%o\", orig_mode);\n> \t}\n> \tif (our_blob) {\n> \t\toid_to_hex_r(oidbuf[1], our_blob);\n> \t\txsnprintf(modebuf[1], \"%o\", our_mode);\n> \t}\n> \t...\n> \n> wouldn't it?\n> \n\nYes, especially since you suggested to error out if an empty oid has a\nnon-empty mode in the second patch.\n\n>> +\treturn run_command_v_opt(arguments, 0);\n>> +}\n>> +\n>> +static int merge_entry(struct index_state *istate, int quiet, int pos,\n>> +\t\t       const char *path, merge_cb cb, void *data)\n> \n> When we use an identifier \"cb\", it typically means callback data,\n> not a callback function which is often called \"fn\".  So, name the\n> type \"merge_fn\" (or \"merge_func\"), and call the parameter \"fn\".\n> \n>> +{\n>> +\tint found = 0;\n>> +\tconst struct object_id *oids[3] = {NULL};\n>> +\tunsigned int modes[3] = {0};\n>> +\n>> +\tdo {\n>> +\t\tconst struct cache_entry *ce = istate->cache[pos];\n>> +\t\tint stage = ce_stage(ce);\n>> +\n>> +\t\tif (strcmp(ce->name, path))\n>> +\t\t\tbreak;\n>> +\t\tfound++;\n>> +\t\toids[stage - 1] = &ce->oid;\n>> +\t\tmodes[stage - 1] = ce->ce_mode;\n>> +\t} while (++pos < istate->cache_nr);\n>> +\tif (!found)\n>> +\t\treturn error(_(\"%s is not in the cache\"), path);\n>> +\n>> +\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n>> +\t\tif (!quiet)\n>> +\t\t\terror(_(\"Merge program failed\"));\n>> +\t\treturn -2;\n>> +\t}\n>> +\n>> +\treturn found;\n>> +}\n> \n> This copies from builtin/merge-index.c::merge_entry().\n> \n>> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n>> +\t\t   const char *path, merge_cb cb, void *data)\n>> +{\n>> +\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n>> +\n>> +\t/*\n>> +\t * If it already exists in the cache as stage0, it's\n>> +\t * already merged and there is nothing to do.\n>> +\t */\n>> +\tif (pos < 0) {\n>> +\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n>> +\t\tif (ret == -1)\n>> +\t\t\treturn -1;\n>> +\t\telse if (ret == -2)\n>> +\t\t\treturn 1;\n>> +\t}\n>> +\treturn 0;\n>> +}\n> \n> Likewise from the same function in that file.\n> \n> Are we removing the \"git merge-index\" program?  Reusing the same\n> identifier for these copied-and-pasted pairs of functions bothers\n> me for two reasons.\n> \n>  - An indentifier that was clear and unique enough in the original\n>    context as a file-scope static may not be a good name as a global\n>    identifier.  \n> \n>  - Having two similar-looking functions with the same name makes\n>    reading and learning the codebase starting at \"git grep\" hits\n>    more difficult than necessary.\n> \n\nI don't plan to remove `git merge-index' -- nor any other program, for\nthat matter.  Why not renaming merge_one_path() and merge_all(),\nmerge_index_path() and merge_all_index()?\n\n>> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n>> +\t      merge_cb cb, void *data)\n>> +{\n>> +\tint err = 0, i, ret;\n>> +\tfor (i = 0; i < istate->cache_nr; i++) {\n>> +\t\tconst struct cache_entry *ce = istate->cache[i];\n>> +\t\tif (!ce_stage(ce))\n>> +\t\t\tcontinue;\n>> +\n>> +\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n>> +\t\tif (ret > 0)\n>> +\t\t\ti += ret - 1;\n>> +\t\telse if (ret == -1)\n>> +\t\t\treturn -1;\n>> +\t\telse if (ret == -2) {\n>> +\t\t\tif (oneshot)\n>> +\t\t\t\terr++;\n>> +\t\t\telse\n>> +\t\t\t\treturn 1;\n>> +\t\t}\n>> +\t}\n>> +\n>> +\treturn err;\n>> +}\n> \n> Likewise.\n> \n>> diff --git a/merge-strategies.h b/merge-strategies.h\n>> index b527d145c7..cf78d7eaf4 100644\n>> --- a/merge-strategies.h\n>> +++ b/merge-strategies.h\n>> @@ -10,4 +10,21 @@ int merge_strategies_one_file(struct repository *r,\n>>  \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n>>  \t\t\t      unsigned int their_mode);\n>>  \n>> +typedef int (*merge_cb)(const struct object_id *orig_blob,\n>> +\t\t\tconst struct object_id *our_blob,\n>> +\t\t\tconst struct object_id *their_blob, const char *path,\n>> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t\tvoid *data);\n> \n> Call it \"merge_one_file_func\", probably.\n> \n>> +int merge_program_cb(const struct object_id *orig_blob,\n> \n> Call it spawn_merge_one_file() perhaps?\n> \n>> +\t\t     const struct object_id *our_blob,\n>> +\t\t     const struct object_id *their_blob, const char *path,\n>> +\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t     void *data);\n>> +\n>> +int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n>> +\t\t   const char *path, merge_cb cb, void *data);\n>> +int merge_all(struct index_state *istate, int oneshot, int quiet,\n>> +\t      merge_cb cb, void *data);\n>>  #endif /* MERGE_STRATEGIES_H */\n\nAck for the rest, the two function names are the only thing I am still\nmissing on this patch right now.\n\nAlban\n\n"},{"id":"409293","messageId":"f321f01d-8071-9be3-7214-f8506081f17c@gmail.com","threadId":"53755","inReplyTo":"xmqqr1pyashe.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v3 05/11] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-06T19:53:18Z","receivedAt":"2020-11-06T19:53:23Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Le 16/10/2020 à 21:19, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> +#include \"cache.h\"\n>> +#include \"builtin.h\"\n>> +#include \"merge-strategies.h\"\n>> +\n>> +static const char builtin_merge_resolve_usage[] =\n>> +\t\"git merge-resolve <bases>... -- <head> <remote>\";\n>> +\n>> +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n>> +{\n>> +\tint i, is_baseless = 1, sep_seen = 0;\n>> +\tconst char *head = NULL;\n>> +\tstruct commit_list *bases = NULL, *remote = NULL;\n>> +\tstruct commit_list **next_base = &bases;\n>> +\n>> +\tif (argc < 5)\n>> +\t\tusage(builtin_merge_resolve_usage);\n>> +\n>> +\tsetup_work_tree();\n>> +\tif (repo_read_index(the_repository) < 0)\n>> +\t\tdie(\"invalid index\");\n>> +\n>> +\t/* The first parameters up to -- are merge bases; the rest are\n>> +\t * heads. */\n> \n> Style (I won't repeat).\n> \n>> +\tfor (i = 1; i < argc; i++) {\n>> +\t\tif (strcmp(argv[i], \"--\") == 0)\n> \n> \tif (!strcmp(...))\n> \n> is more typical than comparing with \"== 0\".\n> \n>> +\t\t\tsep_seen = 1;\n>> +\t\telse if (strcmp(argv[i], \"-h\") == 0)\n>> +\t\t\tusage(builtin_merge_resolve_usage);\n>> +\t\telse if (sep_seen && !head)\n>> +\t\t\thead = argv[i];\n>> +\t\telse if (remote) {\n>> +\t\t\t/* Give up if we are given two or more remotes.\n>> +\t\t\t * Not handling octopus. */\n>> +\t\t\treturn 2;\n>> +\t\t} else {\n>> +\t\t\tstruct object_id oid;\n>> +\n>> +\t\t\tget_oid(argv[i], &oid);\n>> +\t\t\tis_baseless &= sep_seen;\n>> +\n>> +\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n> \n> What is this business about an empty tree about?\n> \n\nI don’t remember my intent here -- perhaps I wanted to avoid merges on\nempty trees…  I’ll remove that from here and merge-octopus.c.\n\n>> +\t\t\t\tstruct commit *commit;\n>> +\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n>> +\n>> +\t\t\t\tif (sep_seen)\n>> +\t\t\t\t\tcommit_list_append(commit, &remote);\n>> +\t\t\t\telse\n>> +\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n>> +\t\t\t}\n>> +\t\t}\n>> +\t}\n>> +\n>> +\t/* Give up if this is a baseless merge. */\n>> +\tif (is_baseless)\n>> +\t\treturn 2;\n> \n> This is quite convoluted.  \n> \n> The original is much more straight-forward.  We just said \"grab\n> everything before we see '--' and call them bases; immediately after\n> '--' is HEAD and everything else is remote.  Now do we have any\n> base?  Otherwise we cannot handle it\".\n> \n> I cannot see an equivalence to it in the rewritten result, with the\n> bit operation with is_baseless and sep_seen.  Wouldn't it be the\n> matter of checking if next_base is NULL, or is there something more\n> subtle that deserves in-code comment going on?\n> \n\nAfter re-reading this many, many weeks later, I can confirm that this is\nconvoluted, and that there is a much better way to perform some checks…\n for instance, checking if `bases' is NULL instead of having\n`is_baseless', or checking after the loop if `remotes->next' is not NULL\nto verify if there is multiple remotes.\n\n> Thanks.\n> \n\nAlban\n\n"},{"id":"409869","messageId":"20201113110428.21265-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 01/12] t6027: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:17Z","receivedAt":"2020-11-13T12:10:51Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6027 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6407-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\nindex 4e6c7cb77e..071d3f7343 100755\n--- a/t/t6407-merge-binary.sh\n+++ b/t/t6407-merge-binary.sh\n@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -35,33 +34,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.20.1\n\n"},{"id":"409870","messageId":"20201113110428.21265-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 02/12] update-index: move add_cacheinfo() to read-cache.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:18Z","receivedAt":"2020-11-13T12:10:52Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves the function add_cacheinfo() that already exists in\nupdate-index.c to update-index.c, renames it add_to_index_cacheinfo(),\nand adds an `istate' parameter.  The new cache entry is returned through\na pointer passed in the parameters.  The return value is either 0\n(success), -1 (invalid path), or -2 (failed to add the file in the\nindex).\n\nThis will become useful in the next commit, when the three-way merge\nwill need to call this function.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/update-index.c | 25 +++++++------------------\n cache.h                |  5 +++++\n read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n 3 files changed, 47 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 79087bccea..44862f5e1d 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -404,27 +404,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n \t\t\t const char *path, int stage)\n {\n-\tint len, option;\n-\tstruct cache_entry *ce;\n+\tint res;\n \n-\tif (!verify_path(path, mode))\n-\t\treturn error(\"Invalid path '%s'\", path);\n-\n-\tlen = strlen(path);\n-\tce = make_empty_cache_entry(&the_index, len);\n-\n-\toidcpy(&ce->oid, oid);\n-\tmemcpy(ce->name, path, len);\n-\tce->ce_flags = create_ce_flags(stage);\n-\tce->ce_namelen = len;\n-\tce->ce_mode = create_ce_mode(mode);\n-\tif (assume_unchanged)\n-\t\tce->ce_flags |= CE_VALID;\n-\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n-\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n-\tif (add_cache_entry(ce, option))\n+\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n+\t\t\t\t     allow_add, allow_replace, NULL);\n+\tif (res == -1)\n+\t\treturn res;\n+\tif (res == -2)\n \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n \t\t\t     path);\n+\n \treport(\"add '%s'\", path);\n \treturn 0;\n }\ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..be16ab3215 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -830,6 +830,11 @@ int remove_file_from_index(struct index_state *, const char *path);\n int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n int add_file_to_index(struct index_state *, const char *path, int flags);\n \n+int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **pce);\n+\n int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..c25f951db4 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1350,6 +1350,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n \treturn 0;\n }\n \n+int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **pce)\n+{\n+\tint len, option;\n+\tstruct cache_entry *ce = NULL;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(stage);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n+\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n+\n+\tif (add_index_entry(istate, ce, option)) {\n+\t\tdiscard_cache_entry(ce);\n+\t\treturn -2;\n+\t}\n+\n+\tif (pce)\n+\t\t*pce = ce;\n+\n+\treturn 0;\n+}\n+\n /*\n  * \"refresh\" does not calculate a new sha1 file or bring the\n  * cache up-to-date for mode/content changes. But what it\n-- \n2.20.1\n\n"},{"id":"409871","messageId":"20201113110428.21265-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201005122646.27994-1-alban.gruin@gmail.com","subject":"[PATCH v4 00/12] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:16Z","receivedAt":"2020-11-13T12:10:53Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In a effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on it.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without any changes.\n\nThis series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).  The\ntip is tagged as \"rewrite-merge-strategies-v4\" at\nhttps://github.com/agrn/git.\n\nChanges since v3:\n\n - [2/12] Move add_cacheinfo() to read-cache.c and rename it\n   add_to_index_cacheinfo().  That way, there is no need to copy it to\n   merge-strategies.c.  It also returns the new cache entry.\n\n - [3/12] Changed SHA1 to \"object name\" in the comments\n\n - [3/12] Error out if an object was not specified but a corresponding\n   mode was.\n\n - [3/12] Add a cache entry parameter to checkout_from_index() to avoid\n   calling index_file_exists(), as all of its callers now have the new\n   cache entry thanks to add_to_index_cacheinfo().\n\n - [3/12] Replace ll_merge() with xdl_merge() in do_merge_one_file().\n\n - [3/12] Fail earlier in the case of a permission conflict in\n   do_merge_one_file().\n\n - [3/12] Use `our_mode' instead of fetching a cache entry to define the\n   mode of a merged file in do_merge_one_file().\n\n - [3/12] Rename merge_strategies_one_file() to merge_three_way().\n\n - [3/12] Reformatted a long chain of if/else if/else blocks.\n\n - [4/12] Rename merge_all() to merge_all_index(), merge_one_path() by\n   merge_index_path(), merge_program_cb() to merge_one_file_spawn(),\n   `merge_cb' to `merge_fn', and the parameters `cb' to `fn'.\n\n - [4/12] Use oid_to_hex_r() instead of oid_to_hex() in\n   merge_one_file_spawn().\n\n - [5/12] Rename merge_one_file_cb() to merge_one_file_func().\n\n - [6/12, 8/12] Enable `USE_THE_INDEX_COMPATIBILITY_MACROS' and use\n   read_cache() instead of repo_read_index().\n\n - [6/12] The parameter parsing has been rewritten to look less\n   convoluted.\n\n - [6/12] Reformatted multi-line comments.\n\n - [7/12] Fixed multiple mistakes in the commit message.\n\n - [8/12] The parameters parsing has been rewritten to look more like\n   builtin/merge-resolve.c.\n\n - [3/12, 6/12, 8/12] Removed obsolete informations from commit\n   messages.\n\nAlban Gruin (12):\n  t6027: modernise tests\n  update-index: move add_cacheinfo() to read-cache.c\n  merge-one-file: rewrite in C\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: don't fork if the requested program is\n    `git-merge-one-file'\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Makefile                        |   7 +-\n builtin.h                       |   3 +\n builtin/merge-index.c           | 102 ++----\n builtin/merge-octopus.c         |  69 ++++\n builtin/merge-one-file.c        |  94 ++++++\n builtin/merge-recursive.c       |  16 +-\n builtin/merge-resolve.c         |  73 ++++\n builtin/merge.c                 |   9 +-\n builtin/update-index.c          |  25 +-\n cache.h                         |   7 +-\n git-merge-octopus.sh            | 112 -------\n git-merge-one-file.sh           | 167 ---------\n git-merge-resolve.sh            |  54 ---\n git.c                           |   3 +\n merge-strategies.c              | 576 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  43 +++\n merge.c                         |  12 +\n read-cache.c                    |  35 ++\n sequencer.c                     |  16 +-\n t/t6407-merge-binary.sh         |  27 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 21 files changed, 987 insertions(+), 465 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v3:\n 1:  08c7df596a =  1:  08c7df596a t6027: modernise tests\n -:  ---------- >  2:  df237da758 update-index: move add_cacheinfo() to read-cache.c\n 2:  ce911c99c0 !  3:  b64bad0d23 merge-one-file: rewrite in C\n    @@ -10,22 +10,23 @@\n         external processes are replaced by calls to functions in libgit.a:\n     \n          - calls to `update-index --add --cacheinfo' are replaced by calls to\n    -       add_cache_entry();\n    +       add_to_index_cacheinfo();\n     \n          - calls to `update-index --remove' are replaced by calls to\n    -       remove_file_from_cache();\n    +       remove_file_from_index();\n     \n          - calls to `checkout-index -u -f' are replaced by calls to\n            checkout_entry();\n     \n          - calls to `unpack-file' and `merge-files' are replaced by calls to\n    -       read_mmblob() and ll_merge(), respectively, to merge files\n    +       read_mmblob() and xdl_merge(), respectively, to merge files\n            in-memory;\n     \n    -     - calls to `checkout-index -f --stage=2' are replaced by calls to\n    -       cache_file_exists();\n    +     - calls to `checkout-index -f --stage=2' are removed, as this is needed\n    +       to have the correct permission bits on the merged file from the\n    +       script, but not in the C version;\n     \n    -     - calls to `update-index' are replaced by calls to add_file_to_cache().\n    +     - calls to `update-index' are replaced by calls to add_file_to_index().\n     \n         The bulk of the rewrite is done in a new file in libgit.a,\n         merge-strategies.c.  This will enable the resolve and octopus strategies\n    @@ -96,9 +97,9 @@\n     + *\n     + * This is the git per-file merge utility, called with\n     + *\n    -+ *   argv[1] - original file SHA1 (or empty)\n    -+ *   argv[2] - file in branch1 SHA1 (or empty)\n    -+ *   argv[3] - file in branch2 SHA1 (or empty)\n    ++ *   argv[1] - original file object name (or empty)\n    ++ *   argv[2] - file in branch1 object name (or empty)\n    ++ *   argv[3] - file in branch2 object name (or empty)\n     + *   argv[4] - pathname in repository\n     + *   argv[5] - original file mode (or empty)\n     + *   argv[6] - file in branch1 mode (or empty)\n    @@ -150,27 +151,29 @@\n     +\n     +\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n     +\n    -+\tif (!get_oid(argv[1], &orig_blob)) {\n    ++\tif (!get_oid_hex(argv[1], &orig_blob)) {\n     +\t\tp_orig_blob = &orig_blob;\n     +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n    -+\t}\n    ++\t} else if (!*argv[1] && *argv[5])\n    ++\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n     +\n    -+\tif (!get_oid(argv[2], &our_blob)) {\n    ++\tif (!get_oid_hex(argv[2], &our_blob)) {\n     +\t\tp_our_blob = &our_blob;\n     +\t\tret = read_mode(\"our\", argv[6], &our_mode);\n    -+\t}\n    ++\t} else if (!*argv[2] && *argv[6])\n    ++\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n     +\n    -+\tif (!get_oid(argv[3], &their_blob)) {\n    ++\tif (!get_oid_hex(argv[3], &their_blob)) {\n     +\t\tp_their_blob = &their_blob;\n     +\t\tret = read_mode(\"their\", argv[7], &their_mode);\n    -+\t}\n    ++\t} else if (!*argv[3] && *argv[7])\n    ++\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n     +\n     +\tif (ret)\n     +\t\treturn ret;\n     +\n    -+\tret = merge_strategies_one_file(the_repository,\n    -+\t\t\t\t\tp_orig_blob, p_our_blob, p_their_blob, argv[4],\n    -+\t\t\t\t\torig_mode, our_mode, their_mode);\n    ++\tret = merge_three_way(the_repository, p_orig_blob, p_our_blob, p_their_blob,\n    ++\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n     +\n     +\tif (ret) {\n     +\t\trollback_lock_file(&lock);\n    @@ -372,55 +375,25 @@\n     @@\n     +#include \"cache.h\"\n     +#include \"dir.h\"\n    -+#include \"ll-merge.h\"\n     +#include \"merge-strategies.h\"\n     +#include \"xdiff-interface.h\"\n     +\n    -+static int add_to_index_cacheinfo(struct index_state *istate,\n    -+\t\t\t\t  unsigned int mode,\n    -+\t\t\t\t  const struct object_id *oid, const char *path)\n    -+{\n    -+\tstruct cache_entry *ce;\n    -+\tint len, option;\n    -+\n    -+\tif (!verify_path(path, mode))\n    -+\t\treturn error(_(\"Invalid path '%s'\"), path);\n    -+\n    -+\tlen = strlen(path);\n    -+\tce = make_empty_cache_entry(istate, len);\n    -+\n    -+\toidcpy(&ce->oid, oid);\n    -+\tmemcpy(ce->name, path, len);\n    -+\tce->ce_flags = create_ce_flags(0);\n    -+\tce->ce_namelen = len;\n    -+\tce->ce_mode = create_ce_mode(mode);\n    -+\tif (assume_unchanged)\n    -+\t\tce->ce_flags |= CE_VALID;\n    -+\toption = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n    -+\tif (add_index_entry(istate, ce, option))\n    -+\t\treturn error(_(\"%s: cannot add to the index\"), path);\n    -+\n    -+\treturn 0;\n    -+}\n    -+\n    -+static int checkout_from_index(struct index_state *istate, const char *path)\n    ++static int checkout_from_index(struct index_state *istate, const char *path,\n    ++\t\t\t       struct cache_entry *ce)\n     +{\n     +\tstruct checkout state = CHECKOUT_INIT;\n    -+\tstruct cache_entry *ce;\n     +\n     +\tstate.istate = istate;\n     +\tstate.force = 1;\n     +\tstate.base_dir = \"\";\n     +\tstate.base_dir_len = 0;\n     +\n    -+\tce = index_file_exists(istate, path, strlen(path), 0);\n     +\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n     +\t\treturn error(_(\"%s: cannot checkout file\"), path);\n     +\treturn 0;\n     +}\n     +\n     +static int merge_one_file_deleted(struct index_state *istate,\n    -+\t\t\t\t  const struct object_id *orig_blob,\n     +\t\t\t\t  const struct object_id *our_blob,\n     +\t\t\t\t  const struct object_id *their_blob, const char *path,\n     +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n    @@ -452,16 +425,15 @@\n     +\tssize_t written;\n     +\tmmbuffer_t result = {NULL, 0};\n     +\tmmfile_t mmfs[3];\n    -+\tstruct ll_merge_options merge_opts = {0};\n    -+\tstruct cache_entry *ce;\n    ++\txmparam_t xmp = {{0}};\n     +\n     +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n     +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n     +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n     +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n    -+\n    -+\tread_mmblob(mmfs + 1, our_blob);\n    -+\tread_mmblob(mmfs + 2, their_blob);\n    ++\telse if (our_mode != their_mode)\n    ++\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n    ++\t\t\t     orig_mode, our_mode, their_mode, path);\n     +\n     +\tif (orig_blob) {\n     +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n    @@ -471,12 +443,14 @@\n     +\t\tread_mmblob(mmfs + 0, &null_oid);\n     +\t}\n     +\n    -+\tmerge_opts.xdl_opts = XDL_MERGE_ZEALOUS_ALNUM;\n    -+\tret = ll_merge(&result, path,\n    -+\t\t       mmfs + 0, \"orig\",\n    -+\t\t       mmfs + 1, \"our\",\n    -+\t\t       mmfs + 2, \"their\",\n    -+\t\t       istate, &merge_opts);\n    ++\tread_mmblob(mmfs + 1, our_blob);\n    ++\tread_mmblob(mmfs + 2, their_blob);\n    ++\n    ++\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n    ++\txmp.style = 0;\n    ++\txmp.favor = 0;\n    ++\n    ++\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n     +\n     +\tfor (i = 0; i < 3; i++)\n     +\t\tfree(mmfs[i].ptr);\n    @@ -484,18 +458,13 @@\n     +\tif (ret < 0) {\n     +\t\tfree(result.ptr);\n     +\t\treturn error(_(\"Failed to execute internal merge\"));\n    ++\t} else if (ret > 0 || !orig_blob) {\n    ++\t\tfree(result.ptr);\n    ++\t\treturn error(_(\"content conflict in %s\"), path);\n     +\t}\n     +\n    -+\t/*\n    -+\t * Create the working tree file, using \"our tree\" version from\n    -+\t * the index, and then store the result of the merge.\n    -+\t */\n    -+\tce = index_file_exists(istate, path, strlen(path), 0);\n    -+\tif (!ce)\n    -+\t\tBUG(\"file is not present in the cache?\");\n    -+\n     +\tunlink(path);\n    -+\tif ((dest = open(path, O_WRONLY | O_CREAT, ce->ce_mode)) < 0) {\n    ++\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n     +\t\tfree(result.ptr);\n     +\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n     +\t}\n    @@ -508,49 +477,42 @@\n     +\tif (written < 0)\n     +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n     +\n    -+\tif (ret != 0 || !orig_blob)\n    -+\t\tret = error(_(\"content conflict in %s\"), path);\n    -+\tif (our_mode != their_mode)\n    -+\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n    -+\t\t\t     orig_mode, our_mode, their_mode, path);\n    -+\tif (ret)\n    -+\t\treturn -1;\n    -+\n     +\treturn add_file_to_index(istate, path, 0);\n     +}\n     +\n    -+int merge_strategies_one_file(struct repository *r,\n    -+\t\t\t      const struct object_id *orig_blob,\n    -+\t\t\t      const struct object_id *our_blob,\n    -+\t\t\t      const struct object_id *their_blob, const char *path,\n    -+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n    -+\t\t\t      unsigned int their_mode)\n    ++int merge_three_way(struct repository *r,\n    ++\t\t    const struct object_id *orig_blob,\n    ++\t\t    const struct object_id *our_blob,\n    ++\t\t    const struct object_id *their_blob, const char *path,\n    ++\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n     +{\n     +\tif (orig_blob &&\n     +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n    -+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob))))\n    ++\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n     +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n    -+\t\treturn merge_one_file_deleted(r->index,\n    -+\t\t\t\t\t      orig_blob, our_blob, their_blob, path,\n    ++\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n     +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n    -+\telse if (!orig_blob && our_blob && !their_blob) {\n    ++\t} else if (!orig_blob && our_blob && !their_blob) {\n     +\t\t/*\n     +\t\t * Added in one.  The other side did not add and we\n     +\t\t * added so there is nothing to be done, except making\n     +\t\t * the path merged.\n     +\t\t */\n    -+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path);\n    ++\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path, 0, 1, 1, NULL);\n     +\t} else if (!orig_blob && !our_blob && their_blob) {\n    ++\t\tstruct cache_entry *ce;\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n     +\n     +\t\tif (file_exists(path))\n     +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n     +\n    -+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path))\n    ++\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path, 0, 1, 1, &ce))\n     +\t\t\treturn -1;\n    -+\t\treturn checkout_from_index(r->index, path);\n    ++\t\treturn checkout_from_index(r->index, path, ce);\n     +\t} else if (!orig_blob && our_blob && their_blob &&\n     +\t\t   oideq(our_blob, their_blob)) {\n    ++\t\tstruct cache_entry *ce;\n    ++\n     +\t\t/* Added in both, identically (check for same permissions). */\n     +\t\tif (our_mode != their_mode)\n     +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n    @@ -559,15 +521,15 @@\n     +\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n     +\n    -+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path))\n    ++\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path, 0, 1, 1, &ce))\n     +\t\t\treturn -1;\n    -+\t\treturn checkout_from_index(r->index, path);\n    -+\t} else if (our_blob && their_blob)\n    ++\t\treturn checkout_from_index(r->index, path, ce);\n    ++\t} else if (our_blob && their_blob) {\n     +\t\t/* Modified in both, but differently. */\n     +\t\treturn do_merge_one_file(r->index,\n     +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n     +\t\t\t\t\t orig_mode, our_mode, their_mode);\n    -+\telse {\n    ++\t} else {\n     +\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n     +\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n     +\n    @@ -595,12 +557,11 @@\n     +\n     +#include \"object.h\"\n     +\n    -+int merge_strategies_one_file(struct repository *r,\n    -+\t\t\t      const struct object_id *orig_blob,\n    -+\t\t\t      const struct object_id *our_blob,\n    -+\t\t\t      const struct object_id *their_blob, const char *path,\n    -+\t\t\t      unsigned int orig_mode, unsigned int our_mode,\n    -+\t\t\t      unsigned int their_mode);\n    ++int merge_three_way(struct repository *r,\n    ++\t\t    const struct object_id *orig_blob,\n    ++\t\t    const struct object_id *our_blob,\n    ++\t\t    const struct object_id *their_blob, const char *path,\n    ++\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n     +\n     +#endif /* MERGE_STRATEGIES_H */\n     \n 3:  7f0999f5a3 !  4:  c5577dc691 merge-index: libify merge_one_path() and merge_all()\n    @@ -9,11 +9,11 @@\n         libgit.a, which means that once rewritten, the strategies would still\n         have to invoke `merge-one-file' by spawning a new process first.\n     \n    -    To avoid this, this moves merge_one_path(), merge_all(), and their\n    -    helpers to merge-strategies.c.  They also take a callback to dictate\n    -    what they should do for each file.  For now, to preserve the behaviour\n    -    of `merge-index', only one callback, launching a new process, is\n    -    defined.\n    +    To avoid this, this moves and renames merge_one_path(), merge_all(), and\n    +    their helpers to merge-strategies.c.  They also take a callback to\n    +    dictate what they should do for each file.  For now, to preserve the\n    +    behaviour of `merge-index', only one callback, launching a new process,\n    +    is defined.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n    @@ -103,15 +103,15 @@\n      \t\t\t}\n      \t\t\tif (!strcmp(arg, \"-a\")) {\n     -\t\t\t\tmerge_all();\n    -+\t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n    -+\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n    ++\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n    ++\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n      \t\t\t\tcontinue;\n      \t\t\t}\n      \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n      \t\t}\n     -\t\tmerge_one_path(arg);\n    -+\t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n    -+\t\t\t\t      merge_program_cb, (void *)pgm);\n    ++\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n    ++\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n      \t}\n     -\tif (err && !quiet)\n     -\t\tdie(\"merge program failed\");\n    @@ -122,45 +122,49 @@\n      --- a/merge-strategies.c\n      +++ b/merge-strategies.c\n     @@\n    + #include \"cache.h\"\n      #include \"dir.h\"\n    - #include \"ll-merge.h\"\n      #include \"merge-strategies.h\"\n     +#include \"run-command.h\"\n      #include \"xdiff-interface.h\"\n      \n    - static int add_to_index_cacheinfo(struct index_state *istate,\n    + static int checkout_from_index(struct index_state *istate, const char *path,\n     @@\n      \n      \treturn 0;\n      }\n     +\n    -+int merge_program_cb(const struct object_id *orig_blob,\n    -+\t\t     const struct object_id *our_blob,\n    -+\t\t     const struct object_id *their_blob, const char *path,\n    -+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    -+\t\t     void *data)\n    ++int merge_one_file_spawn(const struct object_id *orig_blob,\n    ++\t\t\t const struct object_id *our_blob,\n    ++\t\t\t const struct object_id *their_blob, const char *path,\n    ++\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\t void *data)\n     +{\n    -+\tchar ownbuf[3][GIT_MAX_HEXSZ] = {{0}};\n    -+\tconst char *arguments[] = { (char *)data, \"\", \"\", \"\", path,\n    -+\t\t\t\t    ownbuf[0], ownbuf[1], ownbuf[2],\n    -+\t\t\t\t    NULL };\n    ++\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n    ++\tchar modes[3][10] = {{0}};\n    ++\tconst char *arguments[] = { (char *)data, oids[0], oids[1], oids[2],\n    ++\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n     +\n    -+\tif (orig_blob)\n    -+\t\targuments[1] = oid_to_hex(orig_blob);\n    -+\tif (our_blob)\n    -+\t\targuments[2] = oid_to_hex(our_blob);\n    -+\tif (their_blob)\n    -+\t\targuments[3] = oid_to_hex(their_blob);\n    ++\tif (orig_blob) {\n    ++\t\toid_to_hex_r(oids[0], orig_blob);\n    ++\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n    ++\t}\n     +\n    -+\txsnprintf(ownbuf[0], sizeof(ownbuf[0]), \"%o\", orig_mode);\n    -+\txsnprintf(ownbuf[1], sizeof(ownbuf[1]), \"%o\", our_mode);\n    -+\txsnprintf(ownbuf[2], sizeof(ownbuf[2]), \"%o\", their_mode);\n    ++\tif (our_blob) {\n    ++\t\toid_to_hex_r(oids[1], our_blob);\n    ++\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n    ++\t}\n    ++\n    ++\tif (their_blob) {\n    ++\t\toid_to_hex_r(oids[2], their_blob);\n    ++\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n    ++\t}\n     +\n     +\treturn run_command_v_opt(arguments, 0);\n     +}\n     +\n     +static int merge_entry(struct index_state *istate, int quiet, int pos,\n    -+\t\t       const char *path, merge_cb cb, void *data)\n    ++\t\t       const char *path, merge_fn fn, void *data)\n     +{\n     +\tint found = 0;\n     +\tconst struct object_id *oids[3] = {NULL};\n    @@ -179,7 +183,7 @@\n     +\tif (!found)\n     +\t\treturn error(_(\"%s is not in the cache\"), path);\n     +\n    -+\tif (cb(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n    ++\tif (fn(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n     +\t\tif (!quiet)\n     +\t\t\terror(_(\"Merge program failed\"));\n     +\t\treturn -2;\n    @@ -188,8 +192,8 @@\n     +\treturn found;\n     +}\n     +\n    -+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n    -+\t\t   const char *path, merge_cb cb, void *data)\n    ++int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    ++\t\t     const char *path, merge_fn fn, void *data)\n     +{\n     +\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n     +\n    @@ -198,7 +202,7 @@\n     +\t * already merged and there is nothing to do.\n     +\t */\n     +\tif (pos < 0) {\n    -+\t\tret = merge_entry(istate, quiet, -pos - 1, path, cb, data);\n    ++\t\tret = merge_entry(istate, quiet, -pos - 1, path, fn, data);\n     +\t\tif (ret == -1)\n     +\t\t\treturn -1;\n     +\t\telse if (ret == -2)\n    @@ -207,8 +211,8 @@\n     +\treturn 0;\n     +}\n     +\n    -+int merge_all(struct index_state *istate, int oneshot, int quiet,\n    -+\t      merge_cb cb, void *data)\n    ++int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    ++\t\t    merge_fn fn, void *data)\n     +{\n     +\tint err = 0, i, ret;\n     +\tfor (i = 0; i < istate->cache_nr; i++) {\n    @@ -216,7 +220,7 @@\n     +\t\tif (!ce_stage(ce))\n     +\t\t\tcontinue;\n     +\n    -+\t\tret = merge_entry(istate, quiet, i, ce->name, cb, data);\n    ++\t\tret = merge_entry(istate, quiet, i, ce->name, fn, data);\n     +\t\tif (ret > 0)\n     +\t\t\ti += ret - 1;\n     +\t\telse if (ret == -1)\n    @@ -236,24 +240,24 @@\n      --- a/merge-strategies.h\n      +++ b/merge-strategies.h\n     @@\n    - \t\t\t      unsigned int orig_mode, unsigned int our_mode,\n    - \t\t\t      unsigned int their_mode);\n    + \t\t    const struct object_id *their_blob, const char *path,\n    + \t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n      \n    -+typedef int (*merge_cb)(const struct object_id *orig_blob,\n    ++typedef int (*merge_fn)(const struct object_id *orig_blob,\n     +\t\t\tconst struct object_id *our_blob,\n     +\t\t\tconst struct object_id *their_blob, const char *path,\n     +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n     +\t\t\tvoid *data);\n     +\n    -+int merge_program_cb(const struct object_id *orig_blob,\n    -+\t\t     const struct object_id *our_blob,\n    -+\t\t     const struct object_id *their_blob, const char *path,\n    -+\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    -+\t\t     void *data);\n    ++int merge_one_file_spawn(const struct object_id *orig_blob,\n    ++\t\t\t const struct object_id *our_blob,\n    ++\t\t\t const struct object_id *their_blob, const char *path,\n    ++\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\t void *data);\n     +\n    -+int merge_one_path(struct index_state *istate, int oneshot, int quiet,\n    -+\t\t   const char *path, merge_cb cb, void *data);\n    -+int merge_all(struct index_state *istate, int oneshot, int quiet,\n    -+\t      merge_cb cb, void *data);\n    ++int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    ++\t\t     const char *path, merge_fn fn, void *data);\n    ++int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    ++\t\t    merge_fn fn, void *data);\n     +\n      #endif /* MERGE_STRATEGIES_H */\n 4:  c0bc05406d !  5:  a0e6cebe89 merge-index: don't fork if the requested program is `git-merge-one-file'\n    @@ -3,8 +3,8 @@\n         merge-index: don't fork if the requested program is `git-merge-one-file'\n     \n         Since `git-merge-one-file' has been rewritten and libified, this teaches\n    -    `merge-index' to call merge_strategies_one_file() without forking using\n    -    a new callback, merge_one_file_cb().\n    +    `merge-index' to call merge_three_way() without forking using a new\n    +    callback, merge_one_file_func().\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n    @@ -22,7 +22,7 @@\n      \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n      \tconst char *pgm;\n     +\tvoid *data;\n    -+\tmerge_cb merge_action;\n    ++\tmerge_fn merge_action;\n     +\tstruct lock_file lock = LOCK_INIT;\n      \n      \t/* Without this we cannot rely on waitpid() to tell\n    @@ -34,13 +34,13 @@\n     +\n      \tpgm = argv[i++];\n     +\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n    -+\t\tmerge_action = merge_one_file_cb;\n    ++\t\tmerge_action = merge_one_file_func;\n     +\t\tdata = (void *)the_repository;\n     +\n     +\t\tsetup_work_tree();\n     +\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n     +\t} else {\n    -+\t\tmerge_action = merge_program_cb;\n    ++\t\tmerge_action = merge_one_file_spawn;\n     +\t\tdata = (void *)pgm;\n     +\t}\n     +\n    @@ -50,19 +50,19 @@\n     @@\n      \t\t\t}\n      \t\t\tif (!strcmp(arg, \"-a\")) {\n    - \t\t\t\terr |= merge_all(&the_index, one_shot, quiet,\n    --\t\t\t\t\t\t merge_program_cb, (void *)pgm);\n    -+\t\t\t\t\t\t merge_action, data);\n    + \t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n    +-\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n    ++\t\t\t\t\t\t       merge_action, data);\n      \t\t\t\tcontinue;\n      \t\t\t}\n      \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n      \t\t}\n    - \t\terr |= merge_one_path(&the_index, one_shot, quiet, arg,\n    --\t\t\t\t      merge_program_cb, (void *)pgm);\n    -+\t\t\t\t      merge_action, data);\n    + \t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n    +-\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n    ++\t\t\t\t\tmerge_action, data);\n     +\t}\n     +\n    -+\tif (merge_action == merge_one_file_cb) {\n    ++\tif (merge_action == merge_one_file_func) {\n     +\t\tif (err) {\n     +\t\t\trollback_lock_file(&lock);\n     +\t\t\treturn err;\n    @@ -80,20 +80,20 @@\n      \treturn 0;\n      }\n      \n    -+int merge_one_file_cb(const struct object_id *orig_blob,\n    -+\t\t      const struct object_id *our_blob,\n    -+\t\t      const struct object_id *their_blob, const char *path,\n    -+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    -+\t\t      void *data)\n    ++int merge_one_file_func(const struct object_id *orig_blob,\n    ++\t\t\tconst struct object_id *our_blob,\n    ++\t\t\tconst struct object_id *their_blob, const char *path,\n    ++\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\tvoid *data)\n     +{\n    -+\treturn merge_strategies_one_file((struct repository *)data,\n    -+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n    -+\t\t\t\t\t orig_mode, our_mode, their_mode);\n    ++\treturn merge_three_way((struct repository *)data,\n    ++\t\t\t       orig_blob, our_blob, their_blob, path,\n    ++\t\t\t       orig_mode, our_mode, their_mode);\n     +}\n     +\n    - int merge_program_cb(const struct object_id *orig_blob,\n    - \t\t     const struct object_id *our_blob,\n    - \t\t     const struct object_id *their_blob, const char *path,\n    + int merge_one_file_spawn(const struct object_id *orig_blob,\n    + \t\t\t const struct object_id *our_blob,\n    + \t\t\t const struct object_id *their_blob, const char *path,\n     \n      diff --git a/merge-strategies.h b/merge-strategies.h\n      --- a/merge-strategies.h\n    @@ -102,12 +102,12 @@\n      \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n      \t\t\tvoid *data);\n      \n    -+int merge_one_file_cb(const struct object_id *orig_blob,\n    -+\t\t      const struct object_id *our_blob,\n    -+\t\t      const struct object_id *their_blob, const char *path,\n    -+\t\t      unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    -+\t\t      void *data);\n    ++int merge_one_file_func(const struct object_id *orig_blob,\n    ++\t\t\tconst struct object_id *our_blob,\n    ++\t\t\tconst struct object_id *their_blob, const char *path,\n    ++\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\tvoid *data);\n     +\n    - int merge_program_cb(const struct object_id *orig_blob,\n    - \t\t     const struct object_id *our_blob,\n    - \t\t     const struct object_id *their_blob, const char *path,\n    + int merge_one_file_spawn(const struct object_id *orig_blob,\n    + \t\t\t const struct object_id *our_blob,\n    + \t\t\t const struct object_id *their_blob, const char *path,\n 5:  cbfe192982 !  6:  94fbc7e286 merge-resolve: rewrite in C\n    @@ -17,12 +17,10 @@\n            write_index_as_tree().\n     \n          - The call to `merge-index', needed to invoke `git merge-one-file', is\n    -       replaced by a call to the new merge_all() function.  A callback\n    -       function, merge_one_file_cb(), is added to allow it to call\n    -       merge_one_file() without forking.\n    +       replaced by a call to the new merge_all_index() function.\n     \n    -    Here too, the index is read in cmd_merge_resolve(), but\n    -    merge_strategies_resolve() takes care of writing it back to the disk.\n    +    The index is read in cmd_merge_resolve(), and is wrote back by\n    +    merge_strategies_resolve().\n     \n         The parameters of merge_strategies_resolve() will be surprising at first\n         glance: why using a commit list for `bases' and `remote', where we could\n    @@ -83,6 +81,7 @@\n     + * Resolve two trees, using enhanced multi-base read-tree.\n     + */\n     +\n    ++#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     +#include \"cache.h\"\n     +#include \"builtin.h\"\n     +#include \"merge-strategies.h\"\n    @@ -92,7 +91,7 @@\n     +\n     +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n     +{\n    -+\tint i, is_baseless = 1, sep_seen = 0;\n    ++\tint i, sep_seen = 0;\n     +\tconst char *head = NULL;\n     +\tstruct commit_list *bases = NULL, *remote = NULL;\n     +\tstruct commit_list **next_base = &bases;\n    @@ -101,42 +100,45 @@\n     +\t\tusage(builtin_merge_resolve_usage);\n     +\n     +\tsetup_work_tree();\n    -+\tif (repo_read_index(the_repository) < 0)\n    ++\tif (read_cache() < 0)\n     +\t\tdie(\"invalid index\");\n     +\n    -+\t/* The first parameters up to -- are merge bases; the rest are\n    -+\t * heads. */\n    ++\t/*\n    ++\t * The first parameters up to -- are merge bases; the rest are\n    ++\t * heads.\n    ++\t */\n     +\tfor (i = 1; i < argc; i++) {\n    -+\t\tif (strcmp(argv[i], \"--\") == 0)\n    ++\t\tif (!strcmp(argv[i], \"--\"))\n     +\t\t\tsep_seen = 1;\n    -+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n    ++\t\telse if (!strcmp(argv[i], \"-h\"))\n     +\t\t\tusage(builtin_merge_resolve_usage);\n     +\t\telse if (sep_seen && !head)\n     +\t\t\thead = argv[i];\n    -+\t\telse if (remote) {\n    -+\t\t\t/* Give up if we are given two or more remotes.\n    -+\t\t\t * Not handling octopus. */\n    -+\t\t\treturn 2;\n    -+\t\t} else {\n    ++\t\telse {\n     +\t\t\tstruct object_id oid;\n    ++\t\t\tstruct commit *commit;\n     +\n    -+\t\t\tget_oid(argv[i], &oid);\n    -+\t\t\tis_baseless &= sep_seen;\n    ++\t\t\tif (get_oid(argv[i], &oid))\n    ++\t\t\t\tdie(\"object %s not found.\", argv[i]);\n     +\n    -+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n    -+\t\t\t\tstruct commit *commit;\n    -+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n    ++\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n     +\n    -+\t\t\t\tif (sep_seen)\n    -+\t\t\t\t\tcommit_list_append(commit, &remote);\n    -+\t\t\t\telse\n    -+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n    -+\t\t\t}\n    ++\t\t\tif (sep_seen)\n    ++\t\t\t\tcommit_list_insert(commit, &remote);\n    ++\t\t\telse\n    ++\t\t\t\tnext_base = commit_list_append(commit, next_base);\n     +\t\t}\n     +\t}\n     +\n    ++\t/*\n    ++\t * Give up if we are given two or more remotes.  Not handling\n    ++\t * octopus.\n    ++\t */\n    ++\tif (remote && remote->next)\n    ++\t\treturn 2;\n    ++\n     +\t/* Give up if this is a baseless merge. */\n    -+\tif (is_baseless)\n    ++\tif (!bases)\n     +\t\treturn 2;\n     +\n     +\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n    @@ -221,14 +223,13 @@\n      #include \"cache.h\"\n     +#include \"cache-tree.h\"\n      #include \"dir.h\"\n    - #include \"ll-merge.h\"\n     +#include \"lockfile.h\"\n      #include \"merge-strategies.h\"\n      #include \"run-command.h\"\n     +#include \"unpack-trees.h\"\n      #include \"xdiff-interface.h\"\n      \n    - static int add_to_index_cacheinfo(struct index_state *istate,\n    + static int checkout_from_index(struct index_state *istate, const char *path,\n     @@\n      \n      \treturn err;\n    @@ -303,7 +304,7 @@\n     +\n     +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\tret = merge_all(r->index, 0, 0, merge_one_file_cb, r);\n    ++\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n     +\n     +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n     +\t\treturn !!ret;\n    @@ -326,10 +327,10 @@\n     +#include \"commit.h\"\n      #include \"object.h\"\n      \n    - int merge_strategies_one_file(struct repository *r,\n    + int merge_three_way(struct repository *r,\n     @@\n    - int merge_all(struct index_state *istate, int oneshot, int quiet,\n    - \t      merge_cb cb, void *data);\n    + int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    + \t\t    merge_fn fn, void *data);\n      \n     +int merge_strategies_resolve(struct repository *r,\n     +\t\t\t     struct commit_list *bases, const char *head_arg,\n 6:  35e386f626 !  7:  b582b7e5d1 merge-recursive: move better_branch_name() to merge.c\n    @@ -2,8 +2,8 @@\n     \n         merge-recursive: move better_branch_name() to merge.c\n     \n    -    get_better_branch_name() will be used by rebase-octopus once it is\n    -    rewritten in C, so instead of duplicating it, this moves this function\n    +    better_branch_name() will be used by merge-octopus once it is rewritten\n    +    in C, so instead of duplicating it, this moves this function\n         preventively inside an appropriate file in libgit.a.  This function is\n         also renamed to reflect its usage by merge strategies.\n     \n 7:  41eb0f7199 !  8:  d1936645d5 merge-octopus: rewrite in C\n    @@ -13,11 +13,10 @@\n            write_index_as_tree().\n     \n          - The call to `diff-index ...' is replaced by a call to\n    -       repo_index_has_changes(), and is moved from cmd_merge_octopus() to\n    -       merge_octopus().\n    +       repo_index_has_changes().\n     \n          - The call to `merge-index', needed to invoke `git merge-one-file', is\n    -       replaced by a call to merge_all().\n    +       replaced by a call to merge_all_index().\n     \n         The index is read in cmd_merge_octopus(), and is wrote back by\n         merge_strategies_octopus().\n    @@ -75,6 +74,7 @@\n     + * Resolve two or more trees.\n     + */\n     +\n    ++#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     +#include \"cache.h\"\n     +#include \"builtin.h\"\n     +#include \"commit.h\"\n    @@ -94,8 +94,8 @@\n     +\t\tusage(builtin_merge_octopus_usage);\n     +\n     +\tsetup_work_tree();\n    -+\tif (repo_read_index(the_repository) < 0)\n    -+\t\tdie(\"corrupted cache\");\n    ++\tif (read_cache() < 0)\n    ++\t\tdie(\"invalid index\");\n     +\n     +\t/*\n     +\t * The first parameters up to -- are merge bases; the rest are\n    @@ -110,18 +110,17 @@\n     +\t\t\thead_arg = argv[i];\n     +\t\telse {\n     +\t\t\tstruct object_id oid;\n    ++\t\t\tstruct commit *commit;\n     +\n    -+\t\t\tget_oid(argv[i], &oid);\n    ++\t\t\tif (get_oid(argv[i], &oid))\n    ++\t\t\t\tdie(\"object %s not found.\", argv[i]);\n     +\n    -+\t\t\tif (!oideq(&oid, the_hash_algo->empty_tree)) {\n    -+\t\t\t\tstruct commit *commit;\n    -+\t\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n    ++\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n     +\n    -+\t\t\t\tif (sep_seen)\n    -+\t\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n    -+\t\t\t\telse\n    -+\t\t\t\t\tnext_base = commit_list_append(commit, next_base);\n    -+\t\t\t}\n    ++\t\t\tif (sep_seen)\n    ++\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n    ++\t\t\telse\n    ++\t\t\t\tnext_base = commit_list_append(commit, next_base);\n     +\t\t}\n     +\t}\n     +\n    @@ -273,8 +272,8 @@\n      #include \"cache-tree.h\"\n     +#include \"commit-reach.h\"\n      #include \"dir.h\"\n    - #include \"ll-merge.h\"\n      #include \"lockfile.h\"\n    + #include \"merge-strategies.h\"\n     @@\n      \trollback_lock_file(&lock);\n      \treturn 2;\n    @@ -463,7 +462,7 @@\n     +\n     +\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n     +\t\t\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\t\t\tret = !!merge_all(r->index, 0, 0, merge_one_file_cb, r);\n    ++\t\t\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n     +\t\t\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n     +\n     +\t\t\t\twrite_tree(r, &next);\n 8:  8f6c1ac057 =  9:  26b1a3979c merge: use the \"resolve\" strategy without forking\n 9:  b1125261d1 = 10:  23bc9824df merge: use the \"octopus\" strategy without forking\n10:  8d0932fd02 = 11:  3a340f5984 sequencer: use the \"resolve\" strategy without forking\n11:  e304723957 = 12:  ce3723cf34 sequencer: use the \"octopus\" merge strategy without forking\n-- \n2.20.1\n\n"},{"id":"409872","messageId":"20201113110428.21265-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 03/12] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:19Z","receivedAt":"2020-11-13T12:10:58Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_to_index_cacheinfo();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_index();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and xdl_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are removed, as this is needed\n   to have the correct permission bits on the merged file from the\n   script, but not in the C version;\n\n - calls to `update-index' are replaced by calls to add_file_to_index().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   3 +-\n builtin.h                       |   1 +\n builtin/merge-one-file.c        |  94 +++++++++++++++++\n git-merge-one-file.sh           | 167 ------------------------------\n git.c                           |   1 +\n merge-strategies.c              | 173 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  12 +++\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 8 files changed, 284 insertions(+), 169 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex de53954590..6dfdb33cb2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -909,6 +908,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\n@@ -1094,6 +1094,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..4d2cd78856 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -178,6 +178,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..9c21778e1d\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,94 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file object name (or empty)\n+ *   argv[2] - file in branch1 object name (or empty)\n+ *   argv[3] - file in branch2 object name (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+static int read_mode(const char *name, const char *arg, unsigned int *mode)\n+{\n+\tchar *last;\n+\tint ret = 0;\n+\n+\t*mode = strtol(arg, &last, 8);\n+\n+\tif (*last)\n+\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n+\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n+\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n+\n+\treturn ret;\n+}\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (!get_oid_hex(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n+\t} else if (!*argv[1] && *argv[5])\n+\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tret = read_mode(\"our\", argv[6], &our_mode);\n+\t} else if (!*argv[2] && *argv[6])\n+\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\tret = read_mode(\"their\", argv[7], &their_mode);\n+\t} else if (!*argv[3] && *argv[7])\n+\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_three_way(the_repository, p_orig_blob, p_our_blob, p_their_blob,\n+\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex f1e8b56d99..a4d3f98094 100644\n--- a/git.c\n+++ b/git.c\n@@ -540,6 +540,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..f5fdb15bbf\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,173 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int checkout_from_index(struct index_state *istate, const char *path,\n+\t\t\t       struct cache_entry *ce)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\n+\tstate.istate = istate;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tssize_t written;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\telse if (our_mode != their_mode)\n+\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t     orig_mode, our_mode, their_mode, path);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, &null_oid);\n+\t}\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n+\n+\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"Failed to execute internal merge\"));\n+\t} else if (ret > 0 || !orig_blob) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"content conflict in %s\"), path);\n+\t}\n+\n+\tunlink(path);\n+\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n+\t}\n+\n+\twritten = write_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (written < 0)\n+\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_three_way(struct repository *r,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n+\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n+\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\t} else if (!orig_blob && our_blob && !their_blob) {\n+\t\t/*\n+\t\t * Added in one.  The other side did not add and we\n+\t\t * added so there is nothing to be done, except making\n+\t\t * the path merged.\n+\t\t */\n+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path, 0, 1, 1, NULL);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tstruct cache_entry *ce;\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path, 0, 1, 1, &ce))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path, ce);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\tstruct cache_entry *ce;\n+\n+\t\t/* Added in both, identically (check for same permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path, 0, 1, 1, &ce))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path, ce);\n+\t} else if (our_blob && their_blob) {\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(r->index,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\t} else {\n+\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n+\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n+\n+\t\tif (orig_blob)\n+\t\t\toid_to_hex_r(orig_hex, orig_blob);\n+\t\tif (our_blob)\n+\t\t\toid_to_hex_r(our_hex, our_blob);\n+\t\tif (their_blob)\n+\t\t\toid_to_hex_r(their_hex, their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..e624c4f27c\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,12 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+int merge_three_way(struct repository *r,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n+\n+#endif /* MERGE_STRATEGIES_H */\ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2eddcc7664..5fb74e39a0 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.20.1\n\n"},{"id":"409873","messageId":"20201113110428.21265-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 06/12] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:22Z","receivedAt":"2020-11-13T12:10:59Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all_index() function.\n\nThe index is read in cmd_merge_resolve(), and is wrote back by\nmerge_strategies_resolve().\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |  2 +-\n builtin.h               |  1 +\n builtin/merge-resolve.c | 73 +++++++++++++++++++++++++++++++++++\n git-merge-resolve.sh    | 54 --------------------------\n git.c                   |  1 +\n merge-strategies.c      | 85 +++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 7 files changed, 166 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex 6dfdb33cb2..3cc6b192f1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1097,6 +1096,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 4d2cd78856..35e91c16d0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -180,6 +180,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..dca31676b8\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,73 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--\"))\n+\t\t\tsep_seen = 1;\n+\t\telse if (!strcmp(argv[i], \"-h\"))\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tcommit_list_insert(commit, &remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Give up if we are given two or more remotes.  Not handling\n+\t * octopus.\n+\t */\n+\tif (remote && remote->next)\n+\t\treturn 2;\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (!bases)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 343fe7bccd..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex a4d3f98094..64a1a1de41 100644\n--- a/git.c\n+++ b/git.c\n@@ -544,6 +544,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex aa31b7045c..2b34ea0b76 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,7 +1,10 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int checkout_from_index(struct index_state *istate, const char *path,\n@@ -285,3 +288,85 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int add_tree(const struct object_id *oid, struct tree_desc *t)\n+{\n+\tstruct tree *tree;\n+\n+\ttree = parse_tree_indirect(oid);\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tint i = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct object_id head, oid;\n+\tstruct commit_list *j;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\trefresh_index(r->index, 0, NULL, NULL, NULL);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.update = 1;\n+\topts.merge = 1;\n+\topts.aggressive = 1;\n+\n+\tfor (j = bases; j && j->item; j = j->next) {\n+\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n+\t\t\tgoto out;\n+\t}\n+\n+\tif (head_arg && add_tree(&head, t + (i++)))\n+\t\tgoto out;\n+\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n+\t\tgoto out;\n+\n+\tif (i == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (i == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (i >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = i - 1;\n+\t}\n+\n+\tif (unpack_trees(i, t, &opts))\n+\t\tgoto out;\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+\n+ out:\n+\trollback_lock_file(&lock);\n+\treturn 2;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex b69a12b390..4f996261b4 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_three_way(struct repository *r,\n@@ -32,4 +33,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.20.1\n\n"},{"id":"409874","messageId":"20201113110428.21265-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 04/12] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:20Z","receivedAt":"2020-11-13T12:10:59Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves and renames merge_one_path(), merge_all(), and\ntheir helpers to merge-strategies.c.  They also take a callback to\ndictate what they should do for each file.  For now, to preserve the\nbehaviour of `merge-index', only one callback, launching a new process,\nis defined.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c |  77 +++----------------------------\n merge-strategies.c    | 103 ++++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    |  17 +++++++\n 3 files changed, 127 insertions(+), 70 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..49e3382fb9 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,11 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n-#include \"run-command.h\"\n-\n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n-\n-static int merge_entry(int pos, const char *path)\n-{\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n-\n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n-\n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n-\t}\n-}\n+#include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tconst char *pgm;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex f5fdb15bbf..e1d121c993 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"merge-strategies.h\"\n+#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n static int checkout_from_index(struct index_state *istate, const char *path,\n@@ -171,3 +172,105 @@ int merge_three_way(struct repository *r,\n \n \treturn 0;\n }\n+\n+int merge_one_file_spawn(const struct object_id *orig_blob,\n+\t\t\t const struct object_id *our_blob,\n+\t\t\t const struct object_id *their_blob, const char *path,\n+\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t void *data)\n+{\n+\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n+\tchar modes[3][10] = {{0}};\n+\tconst char *arguments[] = { (char *)data, oids[0], oids[1], oids[2],\n+\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n+\n+\tif (orig_blob) {\n+\t\toid_to_hex_r(oids[0], orig_blob);\n+\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n+\t}\n+\n+\tif (our_blob) {\n+\t\toid_to_hex_r(oids[1], our_blob);\n+\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n+\t}\n+\n+\tif (their_blob) {\n+\t\toid_to_hex_r(oids[2], their_blob);\n+\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n+\t}\n+\n+\treturn run_command_v_opt(arguments, 0);\n+}\n+\n+static int merge_entry(struct index_state *istate, int quiet, int pos,\n+\t\t       const char *path, merge_fn fn, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (fn(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\treturn -2;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, quiet, -pos - 1, path, fn, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data)\n+{\n+\tint err = 0, i, ret;\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, quiet, i, ce->name, fn, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2) {\n+\t\t\tif (oneshot)\n+\t\t\t\terr++;\n+\t\t\telse\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex e624c4f27c..d2f52d6792 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -9,4 +9,21 @@ int merge_three_way(struct repository *r,\n \t\t    const struct object_id *their_blob, const char *path,\n \t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n \n+typedef int (*merge_fn)(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_one_file_spawn(const struct object_id *orig_blob,\n+\t\t\t const struct object_id *our_blob,\n+\t\t\t const struct object_id *their_blob, const char *path,\n+\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t void *data);\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data);\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.20.1\n\n"},{"id":"409875","messageId":"20201113110428.21265-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 07/12] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:23Z","receivedAt":"2020-11-13T12:11:00Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"better_branch_name() will be used by merge-octopus once it is rewritten\nin C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex be16ab3215..2d844576ea 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1933,7 +1933,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 5fb88af102..801d673c5f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.20.1\n\n"},{"id":"409877","messageId":"20201113110428.21265-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 08/12] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:24Z","receivedAt":"2020-11-13T12:11:01Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all_index().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_strategies_octopus().\n\nHere to, merge_strategies_octopus() takes two commit lists and a string\nto reduce frictions when try_merge_strategies() will be modified to call\nit directly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  69 ++++++++++++++\n git-merge-octopus.sh    | 112 ----------------------\n git.c                   |   1 +\n merge-strategies.c      | 204 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 279 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3cc6b192f1..2b2bdffafe 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -600,7 +600,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1093,6 +1092,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 35e91c16d0..50225404a0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -176,6 +176,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..ca8f9f345d\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,69 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Reject if this is not an octopus -- resolve should be used\n+\t * instead.\n+\t */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 7d19d37951..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 64a1a1de41..d51fb5d2bf 100644\n--- a/git.c\n+++ b/git.c\n@@ -539,6 +539,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 2b34ea0b76..2ae27f4a80 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"lockfile.h\"\n #include \"merge-strategies.h\"\n@@ -370,3 +371,206 @@ int merge_strategies_resolve(struct repository *r,\n \trollback_lock_file(&lock);\n \treturn 2;\n }\n+\n+static int fast_forward(struct repository *r, const struct object_id *oids,\n+\t\t\tint nr, int aggressive)\n+{\n+\tint i;\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trepo_read_index_preload(r, NULL, 0);\n+\tif (refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL))\n+\t\treturn -1;\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tfor (i = 0; i < nr; i++) {\n+\t\tstruct tree *tree;\n+\t\ttree = parse_tree_indirect(oids + i);\n+\t\tif (parse_tree(tree))\n+\t\t\treturn -1;\n+\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n+\t}\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL);\n+\tif (!ret)\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint non_ff_merge = 0, ret = 0, references = 1;\n+\tstruct commit **reference_commit;\n+\tstruct tree *reference_tree;\n+\tstruct commit_list *j;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n+\treference_commit[0] = lookup_commit_reference(r, &head);\n+\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n+\n+\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n+\tfor (j = remotes; j && j->item; j = j->next) {\n+\t\tstruct commit *c = j->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct commit_list *common, *k;\n+\t\tchar *branch_name;\n+\t\tint can_ff = 1;\n+\n+\t\tif (ret) {\n+\t\t\t/*\n+\t\t\t * We allow only last one to have a\n+\t\t\t * hand-resolvable conflicts.  Last round failed\n+\t\t\t * and we still had a head to merge.\n+\t\t\t */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common)\n+\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n+\n+\t\tif (k) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!non_ff_merge) {\n+\t\t\tint i;\n+\n+\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n+\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n+\t\t\t\t\t       &reference_commit[i]->object.oid);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!non_ff_merge && can_ff) {\n+\t\t\t/*\n+\t\t\t * The first head being merged was a\n+\t\t\t * fast-forward.  Advance the reference commit\n+\t\t\t * to the head being merged, and use that tree\n+\t\t\t * as the intermediate result of the merge.  We\n+\t\t\t * still need to count this as part of the\n+\t\t\t * parent set.\n+\t\t\t */\n+\t\t\tstruct object_id oids[2];\n+\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\t\t\toidcpy(oids, &head);\n+\t\t\toidcpy(oids + 1, oid);\n+\n+\t\t\tret = fast_forward(r, oids, 2, 0);\n+\t\t\tif (ret) {\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\treferences = 0;\n+\t\t\twrite_tree(r, &reference_tree);\n+\t\t} else {\n+\t\t\tint i = 0;\n+\t\t\tstruct tree *next = NULL;\n+\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n+\n+\t\t\tnon_ff_merge = 1;\n+\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\t\t\tfor (k = common; k; k = k->next)\n+\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n+\n+\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n+\t\t\toidcpy(oids + (i++), oid);\n+\n+\t\t\tif (fast_forward(r, oids, i, 1)) {\n+\t\t\t\tret = 2;\n+\n+\t\t\t\tfree(branch_name);\n+\t\t\t\tfree_commit_list(common);\n+\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tif (write_tree(r, &next)) {\n+\t\t\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\t\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\t\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n+\t\t\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\t\t\twrite_tree(r, &next);\n+\t\t\t}\n+\n+\t\t\treference_tree = next;\n+\t\t}\n+\n+\t\treference_commit[references++] = c;\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\t}\n+\n+out:\n+\tfree(reference_commit);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 4f996261b4..05232a5a89 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -36,5 +36,8 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.20.1\n\n"},{"id":"409876","messageId":"20201113110428.21265-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 10/12] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:26Z","receivedAt":"2020-11-13T12:11:04Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ddfefd8ce3..02a2367647 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -744,6 +744,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\"))\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\telse if (!strcmp(strategy, \"octopus\"))\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.20.1\n\n"},{"id":"409879","messageId":"20201113110428.21265-13-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 12/12] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:28Z","receivedAt":"2020-11-13T12:11:06Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ff411d54af..746afad930 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2005,6 +2005,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.20.1\n\n"},{"id":"409878","messageId":"20201113110428.21265-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 05/12] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:21Z","receivedAt":"2020-11-13T12:11:07Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' has been rewritten and libified, this teaches\n`merge-index' to call merge_three_way() without forking using a new\ncallback, merge_one_file_func().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 29 +++++++++++++++++++++++++++--\n merge-strategies.c    | 11 +++++++++++\n merge-strategies.h    |  6 ++++++\n 3 files changed, 44 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 49e3382fb9..e684811d35 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,11 +1,15 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \tconst char *pgm;\n+\tvoid *data;\n+\tmerge_fn merge_action;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -26,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n+\t\tmerge_action = merge_one_file_func;\n+\t\tdata = (void *)the_repository;\n+\n+\t\tsetup_work_tree();\n+\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\t} else {\n+\t\tmerge_action = merge_one_file_spawn;\n+\t\tdata = (void *)pgm;\n+\t}\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -36,13 +52,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n-\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n+\t\t\t\t\t\t       merge_action, data);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n-\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n+\t\t\t\t\tmerge_action, data);\n+\t}\n+\n+\tif (merge_action == merge_one_file_func) {\n+\t\tif (err) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n \t}\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex e1d121c993..aa31b7045c 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -173,6 +173,17 @@ int merge_three_way(struct repository *r,\n \treturn 0;\n }\n \n+int merge_one_file_func(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data)\n+{\n+\treturn merge_three_way((struct repository *)data,\n+\t\t\t       orig_blob, our_blob, their_blob, path,\n+\t\t\t       orig_mode, our_mode, their_mode);\n+}\n+\n int merge_one_file_spawn(const struct object_id *orig_blob,\n \t\t\t const struct object_id *our_blob,\n \t\t\t const struct object_id *their_blob, const char *path,\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex d2f52d6792..b69a12b390 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -15,6 +15,12 @@ typedef int (*merge_fn)(const struct object_id *orig_blob,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_func(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n int merge_one_file_spawn(const struct object_id *orig_blob,\n \t\t\t const struct object_id *our_blob,\n \t\t\t const struct object_id *their_blob, const char *path,\n-- \n2.20.1\n\n"},{"id":"409880","messageId":"20201113110428.21265-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 11/12] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:27Z","receivedAt":"2020-11-13T12:11:09Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e8676e965f..ff411d54af 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -33,6 +33,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2000,9 +2001,15 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.20.1\n\n"},{"id":"409881","messageId":"20201113110428.21265-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v4 09/12] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-13T11:04:25Z","receivedAt":"2020-11-13T12:11:13Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..ddfefd8ce3 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -740,7 +741,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n-\t} else {\n+\t} else if (!strcmp(strategy, \"resolve\"))\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n+\telse {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n \t\t\t\t\t common, head_arg, remoteheads);\n-- \n2.20.1\n\n"},{"id":"409983","messageId":"20201116102158.8365-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 01/12] t6027: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:47Z","receivedAt":"2020-11-16T11:30:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6027 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6407-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\nindex 4e6c7cb77e..071d3f7343 100755\n--- a/t/t6407-merge-binary.sh\n+++ b/t/t6407-merge-binary.sh\n@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -35,33 +34,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.20.1\n\n"},{"id":"409984","messageId":"20201116102158.8365-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201113110428.21265-1-alban.gruin@gmail.com","subject":"[PATCH v5 00/12] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:46Z","receivedAt":"2020-11-16T11:31:00Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In a effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on it.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without any changes.\n\nThis series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).  The\ntip is tagged as \"rewrite-merge-strategies-v5\" at\nhttps://github.com/agrn/git.\n\nChanges since v4:\n\n - [3/12] Split long lines to 80 characters max.\n\n - [6/12, 8/12] Define fast_forward() when rewriting `merge-resolve'\n   instead of `merge-octopus' and use it in merge_strategies_resolve()\n   to reduce code duplication.  This version takes a list `tree_desc'\n   instead of a list of oids.\n\n - [6/12, 8/12] Rename some variables (eg. i -> nr, j -> i, k -> j).\n\n - [8/12] Rewrote the two loops detecting if the merge was a\n   fast-forward, or if a step was already up to date, to make only one\n   less convoluted loop.\n\n - [8/12] Moved the blocks doing a fast-forward and a non-fast-forward\n   merge to their own functions to make the code simpler.  That way,\n   there is no need to free `branch_name' and `common' each time an\n   error is handled.\n\n - [8/12] A call to die has been replaced by an error()/return.\n\n - [9/12, 10/12] Reformatted a chain of if/else if/else blocks.\n\nAlban Gruin (12):\n  t6027: modernise tests\n  update-index: move add_cacheinfo() to read-cache.c\n  merge-one-file: rewrite in C\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: don't fork if the requested program is\n    `git-merge-one-file'\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Makefile                        |   7 +-\n builtin.h                       |   3 +\n builtin/merge-index.c           | 102 ++----\n builtin/merge-octopus.c         |  69 ++++\n builtin/merge-one-file.c        |  94 ++++++\n builtin/merge-recursive.c       |  16 +-\n builtin/merge-resolve.c         |  73 +++++\n builtin/merge.c                 |   7 +\n builtin/update-index.c          |  25 +-\n cache.h                         |   7 +-\n git-merge-octopus.sh            | 112 -------\n git-merge-one-file.sh           | 167 ----------\n git-merge-resolve.sh            |  54 ---\n git.c                           |   3 +\n merge-strategies.c              | 564 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  43 +++\n merge.c                         |  12 +\n read-cache.c                    |  35 ++\n sequencer.c                     |  16 +-\n t/t6407-merge-binary.sh         |  27 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 21 files changed, 974 insertions(+), 464 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v4:\n 1:  08c7df596a =  1:  08c7df596a t6027: modernise tests\n 2:  df237da758 =  2:  df237da758 update-index: move add_cacheinfo() to read-cache.c\n 3:  b64bad0d23 !  3:  eedddde8ea merge-one-file: rewrite in C\n    @@ -498,7 +498,8 @@\n     +\t\t * added so there is nothing to be done, except making\n     +\t\t * the path merged.\n     +\t\t */\n    -+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob, path, 0, 1, 1, NULL);\n    ++\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob,\n    ++\t\t\t\t\t      path, 0, 1, 1, NULL);\n     +\t} else if (!orig_blob && !our_blob && their_blob) {\n     +\t\tstruct cache_entry *ce;\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n    @@ -506,7 +507,8 @@\n     +\t\tif (file_exists(path))\n     +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n     +\n    -+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob, path, 0, 1, 1, &ce))\n    ++\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob,\n    ++\t\t\t\t\t   path, 0, 1, 1, &ce))\n     +\t\t\treturn -1;\n     +\t\treturn checkout_from_index(r->index, path, ce);\n     +\t} else if (!orig_blob && our_blob && their_blob &&\n    @@ -521,7 +523,8 @@\n     +\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n     +\n    -+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob, path, 0, 1, 1, &ce))\n    ++\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob,\n    ++\t\t\t\t\t   path, 0, 1, 1, &ce))\n     +\t\t\treturn -1;\n     +\t\treturn checkout_from_index(r->index, path, ce);\n     +\t} else if (our_blob && their_blob) {\n 4:  c5577dc691 =  4:  a9b9942243 merge-index: libify merge_one_path() and merge_all()\n 5:  a0e6cebe89 =  5:  12775907c5 merge-index: don't fork if the requested program is `git-merge-one-file'\n 6:  94fbc7e286 !  6:  54a4a12504 merge-resolve: rewrite in C\n    @@ -235,72 +235,86 @@\n      \treturn err;\n      }\n     +\n    -+static int add_tree(const struct object_id *oid, struct tree_desc *t)\n    ++static int fast_forward(struct repository *r, struct tree_desc *t,\n    ++\t\t\tint nr, int aggressive)\n     +{\n    -+\tstruct tree *tree;\n    -+\n    -+\ttree = parse_tree_indirect(oid);\n    -+\tif (parse_tree(tree))\n    -+\t\treturn -1;\n    -+\n    -+\tinit_tree_desc(t, tree->buffer, tree->size);\n    -+\treturn 0;\n    -+}\n    -+\n    -+int merge_strategies_resolve(struct repository *r,\n    -+\t\t\t     struct commit_list *bases, const char *head_arg,\n    -+\t\t\t     struct commit_list *remote)\n    -+{\n    -+\tint i = 0;\n    -+\tstruct lock_file lock = LOCK_INIT;\n    -+\tstruct tree_desc t[MAX_UNPACK_TREES];\n     +\tstruct unpack_trees_options opts;\n    -+\tstruct object_id head, oid;\n    -+\tstruct commit_list *j;\n    -+\n    -+\tif (head_arg)\n    -+\t\tget_oid(head_arg, &head);\n    ++\tstruct lock_file lock = LOCK_INIT;\n     +\n    ++\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n     +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\trefresh_index(r->index, 0, NULL, NULL, NULL);\n     +\n     +\tmemset(&opts, 0, sizeof(opts));\n     +\topts.head_idx = 1;\n     +\topts.src_index = r->index;\n     +\topts.dst_index = r->index;\n    -+\topts.update = 1;\n     +\topts.merge = 1;\n    -+\topts.aggressive = 1;\n    ++\topts.update = 1;\n    ++\topts.aggressive = aggressive;\n     +\n    -+\tfor (j = bases; j && j->item; j = j->next) {\n    -+\t\tif (add_tree(&j->item->object.oid, t + (i++)))\n    -+\t\t\tgoto out;\n    -+\t}\n    -+\n    -+\tif (head_arg && add_tree(&head, t + (i++)))\n    -+\t\tgoto out;\n    -+\tif (remote && add_tree(&remote->item->object.oid, t + (i++)))\n    -+\t\tgoto out;\n    -+\n    -+\tif (i == 1)\n    ++\tif (nr == 1)\n     +\t\topts.fn = oneway_merge;\n    -+\telse if (i == 2) {\n    ++\telse if (nr == 2) {\n     +\t\topts.fn = twoway_merge;\n     +\t\topts.initial_checkout = is_index_unborn(r->index);\n    -+\t} else if (i >= 3) {\n    ++\t} else if (nr >= 3) {\n     +\t\topts.fn = threeway_merge;\n    -+\t\topts.head_idx = i - 1;\n    ++\t\topts.head_idx = nr - 1;\n     +\t}\n     +\n    -+\tif (unpack_trees(i, t, &opts))\n    -+\t\tgoto out;\n    ++\tif (unpack_trees(nr, t, &opts))\n    ++\t\treturn -1;\n    ++\n    ++\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n    ++\t\treturn error(_(\"unable to write new index file\"));\n    ++\n    ++\treturn 0;\n    ++}\n    ++\n    ++static int add_tree(struct tree *tree, struct tree_desc *t)\n    ++{\n    ++\tif (parse_tree(tree))\n    ++\t\treturn -1;\n    ++\n    ++\tinit_tree_desc(t, tree->buffer, tree->size);\n    ++\treturn 0;\n    ++}\n    ++\n    ++int merge_strategies_resolve(struct repository *r,\n    ++\t\t\t     struct commit_list *bases, const char *head_arg,\n    ++\t\t\t     struct commit_list *remote)\n    ++{\n    ++\tstruct tree_desc t[MAX_UNPACK_TREES];\n    ++\tstruct object_id head, oid;\n    ++\tstruct commit_list *i;\n    ++\tint nr = 0;\n    ++\n    ++\tif (head_arg)\n    ++\t\tget_oid(head_arg, &head);\n     +\n     +\tputs(_(\"Trying simple merge.\"));\n    -+\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n    ++\n    ++\tfor (i = bases; i && i->item; i = i->next) {\n    ++\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n    ++\t\t\treturn 2;\n    ++\t}\n    ++\n    ++\tif (head_arg) {\n    ++\t\tstruct tree *tree = parse_tree_indirect(&head);\n    ++\t\tif (add_tree(tree, t + (nr++)))\n    ++\t\t\treturn 2;\n    ++\t}\n    ++\n    ++\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n    ++\t\treturn 2;\n    ++\n    ++\tif (fast_forward(r, t, nr, 1))\n    ++\t\treturn 2;\n     +\n     +\tif (write_index_as_tree(&oid, r->index, r->index_file,\n     +\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n     +\t\tint ret;\n    ++\t\tstruct lock_file lock = LOCK_INIT;\n     +\n     +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    @@ -311,10 +325,6 @@\n     +\t}\n     +\n     +\treturn 0;\n    -+\n    -+ out:\n    -+\trollback_lock_file(&lock);\n    -+\treturn 2;\n     +}\n     \n      diff --git a/merge-strategies.h b/merge-strategies.h\n 7:  b582b7e5d1 =  7:  7c4ad06b95 merge-recursive: move better_branch_name() to merge.c\n 8:  d1936645d5 !  8:  edbe08d41b merge-octopus: rewrite in C\n    @@ -275,88 +275,107 @@\n      #include \"lockfile.h\"\n      #include \"merge-strategies.h\"\n     @@\n    - \trollback_lock_file(&lock);\n    - \treturn 2;\n    + \n    + \treturn 0;\n      }\n     +\n    -+static int fast_forward(struct repository *r, const struct object_id *oids,\n    -+\t\t\tint nr, int aggressive)\n    -+{\n    -+\tint i;\n    -+\tstruct tree_desc t[MAX_UNPACK_TREES];\n    -+\tstruct unpack_trees_options opts;\n    -+\tstruct lock_file lock = LOCK_INIT;\n    -+\n    -+\trepo_read_index_preload(r, NULL, 0);\n    -+\tif (refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL))\n    -+\t\treturn -1;\n    -+\n    -+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\n    -+\tmemset(&opts, 0, sizeof(opts));\n    -+\topts.head_idx = 1;\n    -+\topts.src_index = r->index;\n    -+\topts.dst_index = r->index;\n    -+\topts.merge = 1;\n    -+\topts.update = 1;\n    -+\topts.aggressive = aggressive;\n    -+\n    -+\tfor (i = 0; i < nr; i++) {\n    -+\t\tstruct tree *tree;\n    -+\t\ttree = parse_tree_indirect(oids + i);\n    -+\t\tif (parse_tree(tree))\n    -+\t\t\treturn -1;\n    -+\t\tinit_tree_desc(t + i, tree->buffer, tree->size);\n    -+\t}\n    -+\n    -+\tif (nr == 1)\n    -+\t\topts.fn = oneway_merge;\n    -+\telse if (nr == 2) {\n    -+\t\topts.fn = twoway_merge;\n    -+\t\topts.initial_checkout = is_index_unborn(r->index);\n    -+\t} else if (nr >= 3) {\n    -+\t\topts.fn = threeway_merge;\n    -+\t\topts.head_idx = nr - 1;\n    -+\t}\n    -+\n    -+\tif (unpack_trees(nr, t, &opts))\n    -+\t\treturn -1;\n    -+\n    -+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n    -+\t\treturn error(_(\"unable to write new index file\"));\n    -+\n    -+\treturn 0;\n    -+}\n    -+\n     +static int write_tree(struct repository *r, struct tree **reference_tree)\n     +{\n     +\tstruct object_id oid;\n     +\tint ret;\n     +\n    -+\tret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL);\n    -+\tif (!ret)\n    ++\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL)))\n     +\t\t*reference_tree = lookup_tree(r, &oid);\n     +\n     +\treturn ret;\n     +}\n     +\n    ++static int octopus_fast_forward(struct repository *r, const char *branch_name,\n    ++\t\t\t\tstruct tree *tree_head, struct tree *current_tree,\n    ++\t\t\t\tstruct tree **reference_tree)\n    ++{\n    ++\t/*\n    ++\t * The first head being merged was a fast-forward.  Advance the\n    ++\t * reference commit to the head being merged, and use that tree\n    ++\t * as the intermediate result of the merge.  We still need to\n    ++\t * count this as part of the parent set.\n    ++\t */\n    ++\tstruct tree_desc t[2];\n    ++\n    ++\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n    ++\n    ++\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n    ++\tif (add_tree(current_tree, t + 1))\n    ++\t\treturn -1;\n    ++\tif (fast_forward(r, t, 2, 0))\n    ++\t\treturn -1;\n    ++\tif (write_tree(r, reference_tree))\n    ++\t\treturn -1;\n    ++\n    ++\treturn 0;\n    ++}\n    ++\n    ++static int octopus_do_merge(struct repository *r, const char *branch_name,\n    ++\t\t\t    struct commit_list *common, struct tree *current_tree,\n    ++\t\t\t    struct tree **reference_tree)\n    ++{\n    ++\tstruct tree_desc t[MAX_UNPACK_TREES];\n    ++\tstruct commit_list *j;\n    ++\tint nr = 0, ret = 0;\n    ++\n    ++\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n    ++\n    ++\tfor (j = common; j; j = j->next) {\n    ++\t\tstruct tree *tree = repo_get_commit_tree(r, j->item);\n    ++\t\tif (add_tree(tree, t + (nr++)))\n    ++\t\t\treturn -1;\n    ++\t}\n    ++\n    ++\tif (add_tree(*reference_tree, t + (nr++)))\n    ++\t\treturn -1;\n    ++\tif (add_tree(current_tree, t + (nr++)))\n    ++\t\treturn -1;\n    ++\tif (fast_forward(r, t, nr, 1))\n    ++\t\treturn -1;\n    ++\n    ++\tif (write_tree(r, reference_tree)) {\n    ++\t\tstruct lock_file lock = LOCK_INIT;\n    ++\n    ++\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n    ++\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    ++\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n    ++\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n    ++\n    ++\t\twrite_tree(r, reference_tree);\n    ++\t}\n    ++\n    ++\treturn ret ? -2 : 0;\n    ++}\n    ++\n     +int merge_strategies_octopus(struct repository *r,\n     +\t\t\t     struct commit_list *bases, const char *head_arg,\n     +\t\t\t     struct commit_list *remotes)\n     +{\n    -+\tint non_ff_merge = 0, ret = 0, references = 1;\n    ++\tint ff_merge = 1, ret = 0, references = 1;\n     +\tstruct commit **reference_commit;\n    -+\tstruct tree *reference_tree;\n    -+\tstruct commit_list *j;\n    ++\tstruct tree *reference_tree, *tree_head;\n    ++\tstruct commit_list *i;\n     +\tstruct object_id head;\n     +\tstruct strbuf sb = STRBUF_INIT;\n     +\n     +\tget_oid(head_arg, &head);\n     +\n    -+\treference_commit = xcalloc(commit_list_count(remotes) + 1, sizeof(struct commit *));\n    ++\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n    ++\t\t\t\t   sizeof(struct commit *));\n     +\treference_commit[0] = lookup_commit_reference(r, &head);\n     +\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n     +\n    ++\ttree_head = repo_get_commit_tree(r, reference_commit[0]);\n    ++\tif (parse_tree(tree_head)) {\n    ++\t\tret = 2;\n    ++\t\tgoto out;\n    ++\t}\n    ++\n     +\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n     +\t\terror(_(\"Your local changes to the following files \"\n     +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n    @@ -366,12 +385,13 @@\n     +\t\tgoto out;\n     +\t}\n     +\n    -+\tfor (j = remotes; j && j->item; j = j->next) {\n    -+\t\tstruct commit *c = j->item;\n    ++\tfor (i = remotes; i && i->item; i = i->next) {\n    ++\t\tstruct commit *c = i->item;\n     +\t\tstruct object_id *oid = &c->object.oid;\n    -+\t\tstruct commit_list *common, *k;\n    ++\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n    ++\t\tstruct commit_list *common, *j;\n     +\t\tchar *branch_name;\n    -+\t\tint can_ff = 1;\n    ++\t\tint k = 0, up_to_date = 0;\n     +\n     +\t\tif (ret) {\n     +\t\t\t/*\n    @@ -389,92 +409,47 @@\n     +\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n     +\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n     +\n    -+\t\tif (!common)\n    -+\t\t\tdie(_(\"Unable to find common commit with %s\"), branch_name);\n    ++\t\tif (!common) {\n    ++\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n     +\n    -+\t\tfor (k = common; k && !oideq(&k->item->object.oid, oid); k = k->next);\n    ++\t\t\tfree(branch_name);\n    ++\t\t\tfree_commit_list(common);\n     +\n    -+\t\tif (k) {\n    ++\t\t\tret = 2;\n    ++\t\t\tgoto out;\n    ++\t\t}\n    ++\n    ++\t\tfor (j = common; j && !(up_to_date || !ff_merge); j = j->next) {\n    ++\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n    ++\n    ++\t\t\tif (k < references)\n    ++\t\t\t\tff_merge &= oideq(&j->item->object.oid, &reference_commit[k++]->object.oid);\n    ++\t\t}\n    ++\n    ++\t\tif (up_to_date) {\n     +\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n    ++\n     +\t\t\tfree(branch_name);\n     +\t\t\tfree_commit_list(common);\n     +\t\t\tcontinue;\n     +\t\t}\n     +\n    -+\t\tif (!non_ff_merge) {\n    -+\t\t\tint i;\n    -+\n    -+\t\t\tfor (i = 0, k = common; k && i < references && can_ff; k = k->next, i++) {\n    -+\t\t\t\tcan_ff = oideq(&k->item->object.oid,\n    -+\t\t\t\t\t       &reference_commit[i]->object.oid);\n    -+\t\t\t}\n    -+\t\t}\n    -+\n    -+\t\tif (!non_ff_merge && can_ff) {\n    -+\t\t\t/*\n    -+\t\t\t * The first head being merged was a\n    -+\t\t\t * fast-forward.  Advance the reference commit\n    -+\t\t\t * to the head being merged, and use that tree\n    -+\t\t\t * as the intermediate result of the merge.  We\n    -+\t\t\t * still need to count this as part of the\n    -+\t\t\t * parent set.\n    -+\t\t\t */\n    -+\t\t\tstruct object_id oids[2];\n    -+\t\t\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n    -+\n    -+\t\t\toidcpy(oids, &head);\n    -+\t\t\toidcpy(oids + 1, oid);\n    -+\n    -+\t\t\tret = fast_forward(r, oids, 2, 0);\n    -+\t\t\tif (ret) {\n    -+\t\t\t\tfree(branch_name);\n    -+\t\t\t\tfree_commit_list(common);\n    -+\t\t\t\tgoto out;\n    -+\t\t\t}\n    -+\n    ++\t\tif (ff_merge) {\n    ++\t\t\tret = octopus_fast_forward(r, branch_name, tree_head,\n    ++\t\t\t\t\t\t   current_tree, &reference_tree);\n     +\t\t\treferences = 0;\n    -+\t\t\twrite_tree(r, &reference_tree);\n     +\t\t} else {\n    -+\t\t\tint i = 0;\n    -+\t\t\tstruct tree *next = NULL;\n    -+\t\t\tstruct object_id oids[MAX_UNPACK_TREES];\n    -+\n    -+\t\t\tnon_ff_merge = 1;\n    -+\t\t\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n    -+\n    -+\t\t\tfor (k = common; k; k = k->next)\n    -+\t\t\t\toidcpy(oids + (i++), &k->item->object.oid);\n    -+\n    -+\t\t\toidcpy(oids + (i++), &reference_tree->object.oid);\n    -+\t\t\toidcpy(oids + (i++), oid);\n    -+\n    -+\t\t\tif (fast_forward(r, oids, i, 1)) {\n    -+\t\t\t\tret = 2;\n    -+\n    -+\t\t\t\tfree(branch_name);\n    -+\t\t\t\tfree_commit_list(common);\n    -+\n    -+\t\t\t\tgoto out;\n    -+\t\t\t}\n    -+\n    -+\t\t\tif (write_tree(r, &next)) {\n    -+\t\t\t\tstruct lock_file lock = LOCK_INIT;\n    -+\n    -+\t\t\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n    -+\t\t\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\t\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n    -+\t\t\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n    -+\n    -+\t\t\t\twrite_tree(r, &next);\n    -+\t\t\t}\n    -+\n    -+\t\t\treference_tree = next;\n    ++\t\t\tret = octopus_do_merge(r, branch_name, common,\n    ++\t\t\t\t\t       current_tree, &reference_tree);\n     +\t\t}\n     +\n    -+\t\treference_commit[references++] = c;\n    -+\n     +\t\tfree(branch_name);\n     +\t\tfree_commit_list(common);\n    ++\n    ++\t\tif (ret == -1)\n    ++\t\t\tgoto out;\n    ++\n    ++\t\treference_commit[references++] = c;\n     +\t}\n     +\n     +out:\n 9:  26b1a3979c !  9:  e677b27c06 merge: use the \"resolve\" strategy without forking\n    @@ -22,11 +22,9 @@\n      \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n      \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n      \t\treturn clean ? 0 : 1;\n    --\t} else {\n    -+\t} else if (!strcmp(strategy, \"resolve\"))\n    ++\t} else if (!strcmp(strategy, \"resolve\")) {\n     +\t\treturn merge_strategies_resolve(the_repository, common,\n     +\t\t\t\t\t\thead_arg, remoteheads);\n    -+\telse {\n    + \t} else {\n      \t\treturn try_merge_command(the_repository,\n      \t\t\t\t\t strategy, xopts_nr, xopts,\n    - \t\t\t\t\t common, head_arg, remoteheads);\n10:  23bc9824df ! 10:  963f316fd6 merge: use the \"octopus\" strategy without forking\n    @@ -11,12 +11,12 @@\n      --- a/builtin/merge.c\n      +++ b/builtin/merge.c\n     @@\n    - \t} else if (!strcmp(strategy, \"resolve\"))\n    + \t} else if (!strcmp(strategy, \"resolve\")) {\n      \t\treturn merge_strategies_resolve(the_repository, common,\n      \t\t\t\t\t\thead_arg, remoteheads);\n    -+\telse if (!strcmp(strategy, \"octopus\"))\n    ++\t} else if (!strcmp(strategy, \"octopus\")) {\n     +\t\treturn merge_strategies_octopus(the_repository, common,\n     +\t\t\t\t\t\thead_arg, remoteheads);\n    - \telse {\n    + \t} else {\n      \t\treturn try_merge_command(the_repository,\n      \t\t\t\t\t strategy, xopts_nr, xopts,\n11:  3a340f5984 = 11:  0ad967a7e5 sequencer: use the \"resolve\" strategy without forking\n12:  ce3723cf34 = 12:  3814f61717 sequencer: use the \"octopus\" merge strategy without forking\n-- \n2.20.1\n\n"},{"id":"409985","messageId":"20201116102158.8365-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 02/12] update-index: move add_cacheinfo() to read-cache.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:48Z","receivedAt":"2020-11-16T11:31:20Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves the function add_cacheinfo() that already exists in\nupdate-index.c to update-index.c, renames it add_to_index_cacheinfo(),\nand adds an `istate' parameter.  The new cache entry is returned through\na pointer passed in the parameters.  The return value is either 0\n(success), -1 (invalid path), or -2 (failed to add the file in the\nindex).\n\nThis will become useful in the next commit, when the three-way merge\nwill need to call this function.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/update-index.c | 25 +++++++------------------\n cache.h                |  5 +++++\n read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n 3 files changed, 47 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 79087bccea..44862f5e1d 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -404,27 +404,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n \t\t\t const char *path, int stage)\n {\n-\tint len, option;\n-\tstruct cache_entry *ce;\n+\tint res;\n \n-\tif (!verify_path(path, mode))\n-\t\treturn error(\"Invalid path '%s'\", path);\n-\n-\tlen = strlen(path);\n-\tce = make_empty_cache_entry(&the_index, len);\n-\n-\toidcpy(&ce->oid, oid);\n-\tmemcpy(ce->name, path, len);\n-\tce->ce_flags = create_ce_flags(stage);\n-\tce->ce_namelen = len;\n-\tce->ce_mode = create_ce_mode(mode);\n-\tif (assume_unchanged)\n-\t\tce->ce_flags |= CE_VALID;\n-\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n-\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n-\tif (add_cache_entry(ce, option))\n+\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n+\t\t\t\t     allow_add, allow_replace, NULL);\n+\tif (res == -1)\n+\t\treturn res;\n+\tif (res == -2)\n \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n \t\t\t     path);\n+\n \treport(\"add '%s'\", path);\n \treturn 0;\n }\ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..be16ab3215 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -830,6 +830,11 @@ int remove_file_from_index(struct index_state *, const char *path);\n int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n int add_file_to_index(struct index_state *, const char *path, int flags);\n \n+int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **pce);\n+\n int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..c25f951db4 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1350,6 +1350,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n \treturn 0;\n }\n \n+int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **pce)\n+{\n+\tint len, option;\n+\tstruct cache_entry *ce = NULL;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(stage);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n+\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n+\n+\tif (add_index_entry(istate, ce, option)) {\n+\t\tdiscard_cache_entry(ce);\n+\t\treturn -2;\n+\t}\n+\n+\tif (pce)\n+\t\t*pce = ce;\n+\n+\treturn 0;\n+}\n+\n /*\n  * \"refresh\" does not calculate a new sha1 file or bring the\n  * cache up-to-date for mode/content changes. But what it\n-- \n2.20.1\n\n"},{"id":"409986","messageId":"20201116102158.8365-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 04/12] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:50Z","receivedAt":"2020-11-16T11:31:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves and renames merge_one_path(), merge_all(), and\ntheir helpers to merge-strategies.c.  They also take a callback to\ndictate what they should do for each file.  For now, to preserve the\nbehaviour of `merge-index', only one callback, launching a new process,\nis defined.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c |  77 +++----------------------------\n merge-strategies.c    | 103 ++++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    |  17 +++++++\n 3 files changed, 127 insertions(+), 70 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..49e3382fb9 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,11 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n-#include \"run-command.h\"\n-\n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n-\n-static int merge_entry(int pos, const char *path)\n-{\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n-\n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n-\n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n-\t}\n-}\n+#include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tconst char *pgm;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex c5576dc891..4eb96129f1 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"merge-strategies.h\"\n+#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n static int checkout_from_index(struct index_state *istate, const char *path,\n@@ -174,3 +175,105 @@ int merge_three_way(struct repository *r,\n \n \treturn 0;\n }\n+\n+int merge_one_file_spawn(const struct object_id *orig_blob,\n+\t\t\t const struct object_id *our_blob,\n+\t\t\t const struct object_id *their_blob, const char *path,\n+\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t void *data)\n+{\n+\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n+\tchar modes[3][10] = {{0}};\n+\tconst char *arguments[] = { (char *)data, oids[0], oids[1], oids[2],\n+\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n+\n+\tif (orig_blob) {\n+\t\toid_to_hex_r(oids[0], orig_blob);\n+\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n+\t}\n+\n+\tif (our_blob) {\n+\t\toid_to_hex_r(oids[1], our_blob);\n+\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n+\t}\n+\n+\tif (their_blob) {\n+\t\toid_to_hex_r(oids[2], their_blob);\n+\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n+\t}\n+\n+\treturn run_command_v_opt(arguments, 0);\n+}\n+\n+static int merge_entry(struct index_state *istate, int quiet, int pos,\n+\t\t       const char *path, merge_fn fn, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (fn(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\treturn -2;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, quiet, -pos - 1, path, fn, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data)\n+{\n+\tint err = 0, i, ret;\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, quiet, i, ce->name, fn, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (ret == -2) {\n+\t\t\tif (oneshot)\n+\t\t\t\terr++;\n+\t\t\telse\n+\t\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex e624c4f27c..d2f52d6792 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -9,4 +9,21 @@ int merge_three_way(struct repository *r,\n \t\t    const struct object_id *their_blob, const char *path,\n \t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n \n+typedef int (*merge_fn)(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_one_file_spawn(const struct object_id *orig_blob,\n+\t\t\t const struct object_id *our_blob,\n+\t\t\t const struct object_id *their_blob, const char *path,\n+\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t void *data);\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data);\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.20.1\n\n"},{"id":"409987","messageId":"20201116102158.8365-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 03/12] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:49Z","receivedAt":"2020-11-16T11:32:01Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_to_index_cacheinfo();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_index();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and xdl_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are removed, as this is needed\n   to have the correct permission bits on the merged file from the\n   script, but not in the C version;\n\n - calls to `update-index' are replaced by calls to add_file_to_index().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   3 +-\n builtin.h                       |   1 +\n builtin/merge-one-file.c        |  94 +++++++++++++++++\n git-merge-one-file.sh           | 167 ------------------------------\n git.c                           |   1 +\n merge-strategies.c              | 176 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  12 +++\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 8 files changed, 287 insertions(+), 169 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex de53954590..6dfdb33cb2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -909,6 +908,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\n@@ -1094,6 +1094,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..4d2cd78856 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -178,6 +178,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..9c21778e1d\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,94 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file object name (or empty)\n+ *   argv[2] - file in branch1 object name (or empty)\n+ *   argv[3] - file in branch2 object name (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+static int read_mode(const char *name, const char *arg, unsigned int *mode)\n+{\n+\tchar *last;\n+\tint ret = 0;\n+\n+\t*mode = strtol(arg, &last, 8);\n+\n+\tif (*last)\n+\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n+\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n+\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n+\n+\treturn ret;\n+}\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (!get_oid_hex(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n+\t} else if (!*argv[1] && *argv[5])\n+\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tret = read_mode(\"our\", argv[6], &our_mode);\n+\t} else if (!*argv[2] && *argv[6])\n+\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\tret = read_mode(\"their\", argv[7], &their_mode);\n+\t} else if (!*argv[3] && *argv[7])\n+\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_three_way(the_repository, p_orig_blob, p_our_blob, p_their_blob,\n+\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex f1e8b56d99..a4d3f98094 100644\n--- a/git.c\n+++ b/git.c\n@@ -540,6 +540,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..c5576dc891\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,176 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int checkout_from_index(struct index_state *istate, const char *path,\n+\t\t\t       struct cache_entry *ce)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\n+\tstate.istate = istate;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tssize_t written;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\telse if (our_mode != their_mode)\n+\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t     orig_mode, our_mode, their_mode, path);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, &null_oid);\n+\t}\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n+\n+\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"Failed to execute internal merge\"));\n+\t} else if (ret > 0 || !orig_blob) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"content conflict in %s\"), path);\n+\t}\n+\n+\tunlink(path);\n+\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n+\t}\n+\n+\twritten = write_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (written < 0)\n+\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_three_way(struct repository *r,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n+\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n+\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\t} else if (!orig_blob && our_blob && !their_blob) {\n+\t\t/*\n+\t\t * Added in one.  The other side did not add and we\n+\t\t * added so there is nothing to be done, except making\n+\t\t * the path merged.\n+\t\t */\n+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob,\n+\t\t\t\t\t      path, 0, 1, 1, NULL);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tstruct cache_entry *ce;\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob,\n+\t\t\t\t\t   path, 0, 1, 1, &ce))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path, ce);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\tstruct cache_entry *ce;\n+\n+\t\t/* Added in both, identically (check for same permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob,\n+\t\t\t\t\t   path, 0, 1, 1, &ce))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path, ce);\n+\t} else if (our_blob && their_blob) {\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(r->index,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\t} else {\n+\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n+\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n+\n+\t\tif (orig_blob)\n+\t\t\toid_to_hex_r(orig_hex, orig_blob);\n+\t\tif (our_blob)\n+\t\t\toid_to_hex_r(our_hex, our_blob);\n+\t\tif (their_blob)\n+\t\t\toid_to_hex_r(their_hex, their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..e624c4f27c\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,12 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+int merge_three_way(struct repository *r,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n+\n+#endif /* MERGE_STRATEGIES_H */\ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2eddcc7664..5fb74e39a0 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.20.1\n\n"},{"id":"409988","messageId":"20201116102158.8365-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 05/12] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:51Z","receivedAt":"2020-11-16T11:32:21Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' has been rewritten and libified, this teaches\n`merge-index' to call merge_three_way() without forking using a new\ncallback, merge_one_file_func().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 29 +++++++++++++++++++++++++++--\n merge-strategies.c    | 11 +++++++++++\n merge-strategies.h    |  6 ++++++\n 3 files changed, 44 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 49e3382fb9..e684811d35 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,11 +1,15 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \tconst char *pgm;\n+\tvoid *data;\n+\tmerge_fn merge_action;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -26,7 +30,19 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n+\t\tmerge_action = merge_one_file_func;\n+\t\tdata = (void *)the_repository;\n+\n+\t\tsetup_work_tree();\n+\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\t} else {\n+\t\tmerge_action = merge_one_file_spawn;\n+\t\tdata = (void *)pgm;\n+\t}\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -36,13 +52,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n-\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n+\t\t\t\t\t\t       merge_action, data);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n-\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n+\t\t\t\t\tmerge_action, data);\n+\t}\n+\n+\tif (merge_action == merge_one_file_func) {\n+\t\tif (err) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n \t}\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 4eb96129f1..2ed3a8dd68 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -176,6 +176,17 @@ int merge_three_way(struct repository *r,\n \treturn 0;\n }\n \n+int merge_one_file_func(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data)\n+{\n+\treturn merge_three_way((struct repository *)data,\n+\t\t\t       orig_blob, our_blob, their_blob, path,\n+\t\t\t       orig_mode, our_mode, their_mode);\n+}\n+\n int merge_one_file_spawn(const struct object_id *orig_blob,\n \t\t\t const struct object_id *our_blob,\n \t\t\t const struct object_id *their_blob, const char *path,\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex d2f52d6792..b69a12b390 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -15,6 +15,12 @@ typedef int (*merge_fn)(const struct object_id *orig_blob,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_func(const struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n int merge_one_file_spawn(const struct object_id *orig_blob,\n \t\t\t const struct object_id *our_blob,\n \t\t\t const struct object_id *their_blob, const char *path,\n-- \n2.20.1\n\n"},{"id":"409989","messageId":"20201116102158.8365-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 06/12] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:52Z","receivedAt":"2020-11-16T11:32:42Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all_index() function.\n\nThe index is read in cmd_merge_resolve(), and is wrote back by\nmerge_strategies_resolve().\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |  2 +-\n builtin.h               |  1 +\n builtin/merge-resolve.c | 73 +++++++++++++++++++++++++++++++\n git-merge-resolve.sh    | 54 -----------------------\n git.c                   |  1 +\n merge-strategies.c      | 95 +++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 7 files changed, 176 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex 6dfdb33cb2..3cc6b192f1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1097,6 +1096,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 4d2cd78856..35e91c16d0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -180,6 +180,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..dca31676b8\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,73 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--\"))\n+\t\t\tsep_seen = 1;\n+\t\telse if (!strcmp(argv[i], \"-h\"))\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tcommit_list_insert(commit, &remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Give up if we are given two or more remotes.  Not handling\n+\t * octopus.\n+\t */\n+\tif (remote && remote->next)\n+\t\treturn 2;\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (!bases)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 343fe7bccd..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex a4d3f98094..64a1a1de41 100644\n--- a/git.c\n+++ b/git.c\n@@ -544,6 +544,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 2ed3a8dd68..9fafee5954 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,7 +1,10 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int checkout_from_index(struct index_state *istate, const char *path,\n@@ -288,3 +291,95 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int fast_forward(struct repository *r, struct tree_desc *t,\n+\t\t\tint nr, int aggressive)\n+{\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int add_tree(struct tree *tree, struct tree_desc *t)\n+{\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct object_id head, oid;\n+\tstruct commit_list *i;\n+\tint nr = 0;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\n+\tfor (i = bases; i && i->item; i = i->next) {\n+\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (head_arg) {\n+\t\tstruct tree *tree = parse_tree_indirect(&head);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n+\t\treturn 2;\n+\n+\tif (fast_forward(r, t, nr, 1))\n+\t\treturn 2;\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex b69a12b390..4f996261b4 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_three_way(struct repository *r,\n@@ -32,4 +33,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.20.1\n\n"},{"id":"409990","messageId":"20201116102158.8365-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 07/12] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:53Z","receivedAt":"2020-11-16T11:33:02Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"better_branch_name() will be used by merge-octopus once it is rewritten\nin C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex be16ab3215..2d844576ea 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1933,7 +1933,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 5fb88af102..801d673c5f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.20.1\n\n"},{"id":"409991","messageId":"20201116102158.8365-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 08/12] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:54Z","receivedAt":"2020-11-16T11:33:23Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all_index().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_strategies_octopus().\n\nHere to, merge_strategies_octopus() takes two commit lists and a string\nto reduce frictions when try_merge_strategies() will be modified to call\nit directly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  69 ++++++++++++++++\n git-merge-octopus.sh    | 112 -------------------------\n git.c                   |   1 +\n merge-strategies.c      | 179 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 254 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3cc6b192f1..2b2bdffafe 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -600,7 +600,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1093,6 +1092,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 35e91c16d0..50225404a0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -176,6 +176,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..ca8f9f345d\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,69 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Reject if this is not an octopus -- resolve should be used\n+\t * instead.\n+\t */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 7d19d37951..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 64a1a1de41..d51fb5d2bf 100644\n--- a/git.c\n+++ b/git.c\n@@ -539,6 +539,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 9fafee5954..970ff4793d 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"lockfile.h\"\n #include \"merge-strategies.h\"\n@@ -383,3 +384,181 @@ int merge_strategies_resolve(struct repository *r,\n \n \treturn 0;\n }\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL)))\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+static int octopus_fast_forward(struct repository *r, const char *branch_name,\n+\t\t\t\tstruct tree *tree_head, struct tree *current_tree,\n+\t\t\t\tstruct tree **reference_tree)\n+{\n+\t/*\n+\t * The first head being merged was a fast-forward.  Advance the\n+\t * reference commit to the head being merged, and use that tree\n+\t * as the intermediate result of the merge.  We still need to\n+\t * count this as part of the parent set.\n+\t */\n+\tstruct tree_desc t[2];\n+\n+\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n+\tif (add_tree(current_tree, t + 1))\n+\t\treturn -1;\n+\tif (fast_forward(r, t, 2, 0))\n+\t\treturn -1;\n+\tif (write_tree(r, reference_tree))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int octopus_do_merge(struct repository *r, const char *branch_name,\n+\t\t\t    struct commit_list *common, struct tree *current_tree,\n+\t\t\t    struct tree **reference_tree)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct commit_list *j;\n+\tint nr = 0, ret = 0;\n+\n+\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\tfor (j = common; j; j = j->next) {\n+\t\tstruct tree *tree = repo_get_commit_tree(r, j->item);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn -1;\n+\t}\n+\n+\tif (add_tree(*reference_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (add_tree(current_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (fast_forward(r, t, nr, 1))\n+\t\treturn -1;\n+\n+\tif (write_tree(r, reference_tree)) {\n+\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\twrite_tree(r, reference_tree);\n+\t}\n+\n+\treturn ret ? -2 : 0;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint ff_merge = 1, ret = 0, references = 1;\n+\tstruct commit **reference_commit;\n+\tstruct tree *reference_tree, *tree_head;\n+\tstruct commit_list *i;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n+\t\t\t\t   sizeof(struct commit *));\n+\treference_commit[0] = lookup_commit_reference(r, &head);\n+\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n+\n+\ttree_head = repo_get_commit_tree(r, reference_commit[0]);\n+\tif (parse_tree(tree_head)) {\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n+\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\tret = 2;\n+\t\tgoto out;\n+\t}\n+\n+\tfor (i = remotes; i && i->item; i = i->next) {\n+\t\tstruct commit *c = i->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n+\t\tstruct commit_list *common, *j;\n+\t\tchar *branch_name;\n+\t\tint k = 0, up_to_date = 0;\n+\n+\t\tif (ret) {\n+\t\t\t/*\n+\t\t\t * We allow only last one to have a\n+\t\t\t * hand-resolvable conflicts.  Last round failed\n+\t\t\t * and we still had a head to merge.\n+\t\t\t */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common) {\n+\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\n+\t\t\tret = 2;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (j = common; j && !(up_to_date || !ff_merge); j = j->next) {\n+\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n+\n+\t\t\tif (k < references)\n+\t\t\t\tff_merge &= oideq(&j->item->object.oid, &reference_commit[k++]->object.oid);\n+\t\t}\n+\n+\t\tif (up_to_date) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (ff_merge) {\n+\t\t\tret = octopus_fast_forward(r, branch_name, tree_head,\n+\t\t\t\t\t\t   current_tree, &reference_tree);\n+\t\t\treferences = 0;\n+\t\t} else {\n+\t\t\tret = octopus_do_merge(r, branch_name, common,\n+\t\t\t\t\t       current_tree, &reference_tree);\n+\t\t}\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\n+\t\tif (ret == -1)\n+\t\t\tgoto out;\n+\n+\t\treference_commit[references++] = c;\n+\t}\n+\n+out:\n+\tfree(reference_commit);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 4f996261b4..05232a5a89 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -36,5 +36,8 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.20.1\n\n"},{"id":"409992","messageId":"20201116102158.8365-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 11/12] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:57Z","receivedAt":"2020-11-16T11:37:18Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 13 ++++++++++---\n 1 file changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e8676e965f..ff411d54af 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -33,6 +33,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2000,9 +2001,15 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.20.1\n\n"},{"id":"409993","messageId":"20201116102158.8365-13-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 12/12] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:58Z","receivedAt":"2020-11-16T11:37:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ff411d54af..746afad930 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2005,6 +2005,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.20.1\n\n"},{"id":"409994","messageId":"20201116102158.8365-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 10/12] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:56Z","receivedAt":"2020-11-16T11:37:59Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 3b35aa320c..f3345a582a 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -744,6 +744,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\")) {\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\t} else if (!strcmp(strategy, \"octopus\")) {\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.20.1\n\n"},{"id":"409996","messageId":"20201116102158.8365-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v5 09/12] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-16T10:21:55Z","receivedAt":"2020-11-16T11:44:29Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..3b35aa320c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -740,6 +741,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n+\t} else if (!strcmp(strategy, \"resolve\")) {\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.20.1\n\n"},{"id":"410657","messageId":"20201124115315.13311-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 01/13] t6407: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:03Z","receivedAt":"2020-11-24T11:55:07Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6407 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6407-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\nindex 4e6c7cb77e..071d3f7343 100755\n--- a/t/t6407-merge-binary.sh\n+++ b/t/t6407-merge-binary.sh\n@@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files'\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -35,33 +34,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive master\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive master &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410658","messageId":"20201124115315.13311-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201116102158.8365-1-alban.gruin@gmail.com","subject":"[PATCH v6 00/13] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:02Z","receivedAt":"2020-11-24T11:55:07Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In a effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on it.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without any changes.\n\nThis series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).  The\ntip is tagged as \"rewrite-merge-strategies-v6\" at\nhttps://github.com/agrn/git.\n\nChanges since v5:\n\n - [1/13] Change the commit message to reflect the change of the name of\n   t6027.\n\n - [2/13] Introduce changes in t6060 to avoid potential issues with the\n   libified version of merge-index, where a conflicted file could be\n   left unmerged (more details in the commit message).\n\n - [4/13] Fix error handling in do_merge_one_file().\n\n - [5/13] Pass the repository instead of the index to the libified\n   version of merge-index.  This will be useful when merge_three_way()\n   will be modified to put more information in the merge markers:\n   instead of passing the repository as context, an array of two strings\n   will be given to merge_entry()'s callback.\n\n - [5/13] Change error handling in merge_entry().  Instead of returning\n   -2 when the merge program failed, it will increase the number of\n   errors, passed by a pointer.  This change is introduced so the caller\n   (namely merge_all_index()) still knows how many times a file was\n   found in the index with its return value.  When not ran in oneshot\n   mode, the number of errors should remain 0.  merge_entry() should\n   also not print \"Merge program failed\" when ran in oneshot mode.\n\n - [6/13] Fix the issue described in the second patch.\n\n - [7/13] Pass the repository to merge_all_index().\n\n - [9/13] Set the flag WRITE_TREE_SILENT when calling\n   write_index_as_tree().\n\n - [9/13] Cleanup of merge_strategies_octopus() (removing redundant\n   code, removing gotos, etc.).\n\n - [12/13, 13/13] Reformatted an if/else if/else sequence.\n\nAlban Gruin (13):\n  t6407: modernise tests\n  t6060: modify multiple files to expose a possible issue with\n    merge-index\n  update-index: move add_cacheinfo() to read-cache.c\n  merge-one-file: rewrite in C\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: don't fork if the requested program is\n    `git-merge-one-file'\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Makefile                        |   7 +-\n builtin.h                       |   3 +\n builtin/merge-index.c           | 101 ++----\n builtin/merge-octopus.c         |  69 ++++\n builtin/merge-one-file.c        |  94 ++++++\n builtin/merge-recursive.c       |  16 +-\n builtin/merge-resolve.c         |  73 ++++\n builtin/merge.c                 |   7 +\n builtin/update-index.c          |  25 +-\n cache.h                         |   7 +-\n git-merge-octopus.sh            | 112 -------\n git-merge-one-file.sh           | 167 ----------\n git-merge-resolve.sh            |  54 ---\n git.c                           |   3 +\n merge-strategies.c              | 571 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  46 +++\n merge.c                         |  12 +\n read-cache.c                    |  35 ++\n sequencer.c                     |  17 +-\n t/t6060-merge-index.sh          |  10 +-\n t/t6407-merge-binary.sh         |  27 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 22 files changed, 992 insertions(+), 466 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v5:\n 1:  08c7df596a !  1:  70d6507330 t6027: modernise tests\n    @@ Metadata\n     Author: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## Commit message ##\n    -    t6027: modernise tests\n    +    t6407: modernise tests\n     \n    -    Some tests in t6027 uses a if/then/else to check if a command failed or\n    +    Some tests in t6407 uses a if/then/else to check if a command failed or\n         not, but we have the `test_must_fail' function to do it correctly for us\n         nowadays.\n     \n -:  ---------- >  2:  25e9c47e41 t6060: modify multiple files to expose a possible issue with merge-index\n 2:  df237da758 =  3:  e7ea43c5ff update-index: move add_cacheinfo() to read-cache.c\n 3:  eedddde8ea !  4:  284fc4227f merge-one-file: rewrite in C\n    @@ merge-strategies.c (new)\n     +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n     +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n     +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n    -+\telse if (our_mode != their_mode)\n    -+\t\treturn error(_(\"permission conflict: %o->%o,%o in %s\"),\n    -+\t\t\t     orig_mode, our_mode, their_mode, path);\n     +\n     +\tif (orig_blob) {\n     +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n    @@ merge-strategies.c (new)\n     +\tif (ret < 0) {\n     +\t\tfree(result.ptr);\n     +\t\treturn error(_(\"Failed to execute internal merge\"));\n    -+\t} else if (ret > 0 || !orig_blob) {\n    -+\t\tfree(result.ptr);\n    -+\t\treturn error(_(\"content conflict in %s\"), path);\n     +\t}\n     +\n    ++\tif (ret > 0 || !orig_blob)\n    ++\t\tret = error(_(\"content conflict in %s\"), path);\n    ++\tif (our_mode != their_mode)\n    ++\t\tret = error(_(\"permission conflict: %o->%o,%o in %s\"),\n    ++\t\t\t    orig_mode, our_mode, their_mode, path);\n    ++\n     +\tunlink(path);\n     +\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n     +\t\tfree(result.ptr);\n    @@ merge-strategies.c (new)\n     +\n     +\tif (written < 0)\n     +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n    ++\tif (ret)\n    ++\t\treturn ret;\n     +\n     +\treturn add_file_to_index(istate, path, 0);\n     +}\n 4:  a9b9942243 !  5:  54abee902f merge-index: libify merge_one_path() and merge_all()\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n      \t\t\t}\n      \t\t\tif (!strcmp(arg, \"-a\")) {\n     -\t\t\t\tmerge_all();\n    -+\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n    ++\t\t\t\terr |= merge_all_index(the_repository, one_shot, quiet,\n     +\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n      \t\t\t\tcontinue;\n      \t\t\t}\n      \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n      \t\t}\n     -\t\tmerge_one_path(arg);\n    -+\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n    ++\t\terr |= merge_index_path(the_repository, one_shot, quiet, arg,\n     +\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n      \t}\n     -\tif (err && !quiet)\n    @@ merge-strategies.c: int merge_three_way(struct repository *r,\n      \treturn 0;\n      }\n     +\n    -+int merge_one_file_spawn(const struct object_id *orig_blob,\n    ++int merge_one_file_spawn(struct repository *r,\n    ++\t\t\t const struct object_id *orig_blob,\n     +\t\t\t const struct object_id *our_blob,\n     +\t\t\t const struct object_id *their_blob, const char *path,\n     +\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    @@ merge-strategies.c: int merge_three_way(struct repository *r,\n     +\treturn run_command_v_opt(arguments, 0);\n     +}\n     +\n    -+static int merge_entry(struct index_state *istate, int quiet, int pos,\n    -+\t\t       const char *path, merge_fn fn, void *data)\n    ++static int merge_entry(struct repository *r, int quiet, unsigned int pos,\n    ++\t\t       const char *path, int *err, merge_fn fn, void *data)\n     +{\n     +\tint found = 0;\n     +\tconst struct object_id *oids[3] = {NULL};\n     +\tunsigned int modes[3] = {0};\n     +\n     +\tdo {\n    -+\t\tconst struct cache_entry *ce = istate->cache[pos];\n    ++\t\tconst struct cache_entry *ce = r->index->cache[pos];\n     +\t\tint stage = ce_stage(ce);\n     +\n     +\t\tif (strcmp(ce->name, path))\n    @@ merge-strategies.c: int merge_three_way(struct repository *r,\n     +\t\tfound++;\n     +\t\toids[stage - 1] = &ce->oid;\n     +\t\tmodes[stage - 1] = ce->ce_mode;\n    -+\t} while (++pos < istate->cache_nr);\n    ++\t} while (++pos < r->index->cache_nr);\n     +\tif (!found)\n     +\t\treturn error(_(\"%s is not in the cache\"), path);\n     +\n    -+\tif (fn(oids[0], oids[1], oids[2], path, modes[0], modes[1], modes[2], data)) {\n    ++\tif (fn(r, oids[0], oids[1], oids[2], path,\n    ++\t       modes[0], modes[1], modes[2], data)) {\n     +\t\tif (!quiet)\n     +\t\t\terror(_(\"Merge program failed\"));\n    -+\t\treturn -2;\n    ++\t\t(*err)++;\n     +\t}\n     +\n     +\treturn found;\n     +}\n     +\n    -+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    ++int merge_index_path(struct repository *r, int oneshot, int quiet,\n     +\t\t     const char *path, merge_fn fn, void *data)\n     +{\n    -+\tint pos = index_name_pos(istate, path, strlen(path)), ret;\n    ++\tint pos = index_name_pos(r->index, path, strlen(path)), ret, err = 0;\n     +\n     +\t/*\n     +\t * If it already exists in the cache as stage0, it's\n     +\t * already merged and there is nothing to do.\n     +\t */\n     +\tif (pos < 0) {\n    -+\t\tret = merge_entry(istate, quiet, -pos - 1, path, fn, data);\n    ++\t\tret = merge_entry(r, quiet || oneshot, -pos - 1, path, &err, fn, data);\n     +\t\tif (ret == -1)\n     +\t\t\treturn -1;\n    -+\t\telse if (ret == -2)\n    ++\t\telse if (err)\n     +\t\t\treturn 1;\n     +\t}\n     +\treturn 0;\n     +}\n     +\n    -+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    ++int merge_all_index(struct repository *r, int oneshot, int quiet,\n     +\t\t    merge_fn fn, void *data)\n     +{\n    -+\tint err = 0, i, ret;\n    -+\tfor (i = 0; i < istate->cache_nr; i++) {\n    -+\t\tconst struct cache_entry *ce = istate->cache[i];\n    ++\tint err = 0, ret;\n    ++\tunsigned int i;\n    ++\n    ++\tfor (i = 0; i < r->index->cache_nr; i++) {\n    ++\t\tconst struct cache_entry *ce = r->index->cache[i];\n     +\t\tif (!ce_stage(ce))\n     +\t\t\tcontinue;\n     +\n    -+\t\tret = merge_entry(istate, quiet, i, ce->name, fn, data);\n    ++\t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n     +\t\tif (ret > 0)\n     +\t\t\ti += ret - 1;\n     +\t\telse if (ret == -1)\n     +\t\t\treturn -1;\n    -+\t\telse if (ret == -2) {\n    -+\t\t\tif (oneshot)\n    -+\t\t\t\terr++;\n    -+\t\t\telse\n    -+\t\t\t\treturn 1;\n    -+\t\t}\n    ++\n    ++\t\tif (err && !oneshot)\n    ++\t\t\treturn 1;\n     +\t}\n     +\n     +\treturn err;\n    @@ merge-strategies.h: int merge_three_way(struct repository *r,\n      \t\t    const struct object_id *their_blob, const char *path,\n      \t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n      \n    -+typedef int (*merge_fn)(const struct object_id *orig_blob,\n    ++typedef int (*merge_fn)(struct repository *r,\n    ++\t\t\tconst struct object_id *orig_blob,\n     +\t\t\tconst struct object_id *our_blob,\n     +\t\t\tconst struct object_id *their_blob, const char *path,\n     +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n     +\t\t\tvoid *data);\n     +\n    -+int merge_one_file_spawn(const struct object_id *orig_blob,\n    ++int merge_one_file_spawn(struct repository *r,\n    ++\t\t\t const struct object_id *orig_blob,\n     +\t\t\t const struct object_id *our_blob,\n     +\t\t\t const struct object_id *their_blob, const char *path,\n     +\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n     +\t\t\t void *data);\n     +\n    -+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    ++int merge_index_path(struct repository *r, int oneshot, int quiet,\n     +\t\t     const char *path, merge_fn fn, void *data);\n    -+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    ++int merge_all_index(struct repository *r, int oneshot, int quiet,\n     +\t\t    merge_fn fn, void *data);\n     +\n      #endif /* MERGE_STRATEGIES_H */\n 5:  12775907c5 !  6:  acaf100edd merge-index: don't fork if the requested program is `git-merge-one-file'\n    @@ Commit message\n         `merge-index' to call merge_three_way() without forking using a new\n         callback, merge_one_file_func().\n     \n    +    To avoid any issue with a shrinking index because of the merge function\n    +    used (directly in the process or by forking), as described earlier, the\n    +    iterator of the loop of merge_all_index() is increased by the number of\n    +    entries with the same name, minus the difference between the number of\n    +    entries in the index before and after the merge.\n    +\n    +    This should handle a shrinking index correctly, but could lead to issues\n    +    with a growing index.  However, this case is not treated, as there is no\n    +    callback that can produce such a case.\n    +\n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## builtin/merge-index.c ##\n    @@ builtin/merge-index.c\n      {\n      \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n      \tconst char *pgm;\n    -+\tvoid *data;\n    ++\tvoid *data = NULL;\n     +\tmerge_fn merge_action;\n     +\tstruct lock_file lock = LOCK_INIT;\n      \n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n      \t}\n     +\n      \tpgm = argv[i++];\n    ++\tsetup_work_tree();\n    ++\n     +\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n     +\t\tmerge_action = merge_one_file_func;\n    -+\t\tdata = (void *)the_repository;\n    -+\n    -+\t\tsetup_work_tree();\n     +\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n     +\t} else {\n     +\t\tmerge_action = merge_one_file_spawn;\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n      \t\t\t}\n      \t\t\tif (!strcmp(arg, \"-a\")) {\n    - \t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n    + \t\t\t\terr |= merge_all_index(the_repository, one_shot, quiet,\n     -\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n     +\t\t\t\t\t\t       merge_action, data);\n      \t\t\t\tcontinue;\n      \t\t\t}\n      \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n      \t\t}\n    - \t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n    + \t\terr |= merge_index_path(the_repository, one_shot, quiet, arg,\n     -\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n     +\t\t\t\t\tmerge_action, data);\n     +\t}\n    @@ merge-strategies.c: int merge_three_way(struct repository *r,\n      \treturn 0;\n      }\n      \n    -+int merge_one_file_func(const struct object_id *orig_blob,\n    ++int merge_one_file_func(struct repository *r,\n    ++\t\t\tconst struct object_id *orig_blob,\n     +\t\t\tconst struct object_id *our_blob,\n     +\t\t\tconst struct object_id *their_blob, const char *path,\n     +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n     +\t\t\tvoid *data)\n     +{\n    -+\treturn merge_three_way((struct repository *)data,\n    ++\treturn merge_three_way(r,\n     +\t\t\t       orig_blob, our_blob, their_blob, path,\n     +\t\t\t       orig_mode, our_mode, their_mode);\n     +}\n     +\n    - int merge_one_file_spawn(const struct object_id *orig_blob,\n    + int merge_one_file_spawn(struct repository *r,\n    + \t\t\t const struct object_id *orig_blob,\n      \t\t\t const struct object_id *our_blob,\n    - \t\t\t const struct object_id *their_blob, const char *path,\n    +@@ merge-strategies.c: int merge_all_index(struct repository *r, int oneshot, int quiet,\n    + \t\t    merge_fn fn, void *data)\n    + {\n    + \tint err = 0, ret;\n    +-\tunsigned int i;\n    ++\tunsigned int i, prev_nr;\n    + \n    + \tfor (i = 0; i < r->index->cache_nr; i++) {\n    + \t\tconst struct cache_entry *ce = r->index->cache[i];\n    + \t\tif (!ce_stage(ce))\n    + \t\t\tcontinue;\n    + \n    ++\t\tprev_nr = r->index->cache_nr;\n    + \t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n    +-\t\tif (ret > 0)\n    +-\t\t\ti += ret - 1;\n    +-\t\telse if (ret == -1)\n    ++\t\tif (ret > 0) {\n    ++\t\t\t/* Don't bother handling an index that has\n    ++\t\t\t   grown, since merge_one_file_func() can't grow\n    ++\t\t\t   it, and merge_one_file_spawn() can't change\n    ++\t\t\t   it. */\n    ++\t\t\ti += ret - (prev_nr - r->index->cache_nr) - 1;\n    ++\t\t} else if (ret == -1)\n    + \t\t\treturn -1;\n    + \n    + \t\tif (err && !oneshot)\n     \n      ## merge-strategies.h ##\n    -@@ merge-strategies.h: typedef int (*merge_fn)(const struct object_id *orig_blob,\n    +@@ merge-strategies.h: typedef int (*merge_fn)(struct repository *r,\n      \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n      \t\t\tvoid *data);\n      \n    -+int merge_one_file_func(const struct object_id *orig_blob,\n    ++int merge_one_file_func(struct repository *r,\n    ++\t\t\tconst struct object_id *orig_blob,\n     +\t\t\tconst struct object_id *our_blob,\n     +\t\t\tconst struct object_id *their_blob, const char *path,\n     +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n     +\t\t\tvoid *data);\n     +\n    - int merge_one_file_spawn(const struct object_id *orig_blob,\n    + int merge_one_file_spawn(struct repository *r,\n    + \t\t\t const struct object_id *orig_blob,\n      \t\t\t const struct object_id *our_blob,\n    - \t\t\t const struct object_id *their_blob, const char *path,\n 6:  54a4a12504 !  7:  9a9e3faeff merge-resolve: rewrite in C\n    @@ merge-strategies.c\n      #include \"xdiff-interface.h\"\n      \n      static int checkout_from_index(struct index_state *istate, const char *path,\n    -@@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    +@@ merge-strategies.c: int merge_all_index(struct repository *r, int oneshot, int quiet,\n      \n      \treturn err;\n      }\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     +\n     +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n    ++\t\tret = merge_all_index(r, 1, 0, merge_one_file_func, NULL);\n     +\n     +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n     +\t\treturn !!ret;\n    @@ merge-strategies.h\n      #include \"object.h\"\n      \n      int merge_three_way(struct repository *r,\n    -@@ merge-strategies.h: int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    - int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    +@@ merge-strategies.h: int merge_index_path(struct repository *r, int oneshot, int quiet,\n    + int merge_all_index(struct repository *r, int oneshot, int quiet,\n      \t\t    merge_fn fn, void *data);\n      \n     +int merge_strategies_resolve(struct repository *r,\n 7:  7c4ad06b95 =  8:  359346229c merge-recursive: move better_branch_name() to merge.c\n 8:  edbe08d41b !  9:  4dff780212 merge-octopus: rewrite in C\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\tstruct object_id oid;\n     +\tint ret;\n     +\n    -+\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file, 0, NULL)))\n    ++\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file,\n    ++\t\t\t\t\tWRITE_TREE_SILENT, NULL)))\n     +\t\t*reference_tree = lookup_tree(r, &oid);\n     +\n     +\treturn ret;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\n     +\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\tret = merge_all_index(r->index, 0, 0, merge_one_file_func, r);\n    ++\t\tret = merge_all_index(r, 1, 0, merge_one_file_func, NULL);\n     +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n     +\n     +\t\twrite_tree(r, reference_tree);\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t\t     struct commit_list *remotes)\n     +{\n     +\tint ff_merge = 1, ret = 0, references = 1;\n    -+\tstruct commit **reference_commit;\n    -+\tstruct tree *reference_tree, *tree_head;\n    ++\tstruct commit **reference_commit, *head_commit;\n    ++\tstruct tree *reference_tree, *head_tree;\n     +\tstruct commit_list *i;\n     +\tstruct object_id head;\n     +\tstruct strbuf sb = STRBUF_INIT;\n     +\n     +\tget_oid(head_arg, &head);\n    ++\thead_commit = lookup_commit_reference(r, &head);\n    ++\thead_tree = repo_get_commit_tree(r, head_commit);\n     +\n    -+\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n    -+\t\t\t\t   sizeof(struct commit *));\n    -+\treference_commit[0] = lookup_commit_reference(r, &head);\n    -+\treference_tree = repo_get_commit_tree(r, reference_commit[0]);\n    ++\tif (parse_tree(head_tree))\n    ++\t\treturn 2;\n     +\n    -+\ttree_head = repo_get_commit_tree(r, reference_commit[0]);\n    -+\tif (parse_tree(tree_head)) {\n    -+\t\tret = 2;\n    -+\t\tgoto out;\n    -+\t}\n    -+\n    -+\tif (repo_index_has_changes(r, reference_tree, &sb)) {\n    ++\tif (repo_index_has_changes(r, head_tree, &sb)) {\n     +\t\terror(_(\"Your local changes to the following files \"\n     +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n     +\t\t      sb.buf);\n     +\t\tstrbuf_release(&sb);\n    -+\t\tret = 2;\n    -+\t\tgoto out;\n    ++\t\treturn 2;\n     +\t}\n     +\n    ++\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n    ++\t\t\t\t   sizeof(struct commit *));\n    ++\treference_commit[0] = head_commit;\n    ++\treference_tree = head_tree;\n    ++\n     +\tfor (i = remotes; i && i->item; i = i->next) {\n     +\t\tstruct commit *c = i->item;\n     +\t\tstruct object_id *oid = &c->object.oid;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t\tputs(_(\"Automated merge did not work.\"));\n     +\t\t\tputs(_(\"Should not be doing an octopus.\"));\n     +\n    -+\t\t\tret = 2;\n    -+\t\t\tgoto out;\n    ++\t\t\tfree(reference_commit);\n    ++\t\t\treturn 2;\n     +\t\t}\n     +\n     +\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\n     +\t\t\tfree(branch_name);\n     +\t\t\tfree_commit_list(common);\n    ++\t\t\tfree(reference_commit);\n     +\n    -+\t\t\tret = 2;\n    -+\t\t\tgoto out;\n    ++\t\t\treturn 2;\n     +\t\t}\n     +\n     +\t\tfor (j = common; j && !(up_to_date || !ff_merge); j = j->next) {\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t}\n     +\n     +\t\tif (ff_merge) {\n    -+\t\t\tret = octopus_fast_forward(r, branch_name, tree_head,\n    ++\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n     +\t\t\t\t\t\t   current_tree, &reference_tree);\n     +\t\t\treferences = 0;\n     +\t\t} else {\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\tfree_commit_list(common);\n     +\n     +\t\tif (ret == -1)\n    -+\t\t\tgoto out;\n    ++\t\t\tbreak;\n     +\n     +\t\treference_commit[references++] = c;\n     +\t}\n     +\n    -+out:\n     +\tfree(reference_commit);\n     +\treturn ret;\n     +}\n     \n      ## merge-strategies.h ##\n    -@@ merge-strategies.h: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    +@@ merge-strategies.h: int merge_all_index(struct repository *r, int oneshot, int quiet,\n      int merge_strategies_resolve(struct repository *r,\n      \t\t\t     struct commit_list *bases, const char *head_arg,\n      \t\t\t     struct commit_list *remote);\n 9:  e677b27c06 = 10:  76f02b4531 merge: use the \"resolve\" strategy without forking\n10:  963f316fd6 = 11:  c9e0a38d0f merge: use the \"octopus\" strategy without forking\n11:  0ad967a7e5 ! 12:  5b595efa46 sequencer: use the \"resolve\" strategy without forking\n    @@ sequencer.c: static int do_pick_commit(struct repository *r,\n     +\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n     +\t\t\trepo_read_index(r);\n     +\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n    -+\t\t} else\n    ++\t\t} else {\n     +\t\t\tres |= try_merge_command(r, opts->strategy,\n     +\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n     +\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n    ++\t\t}\n     +\n      \t\tfree_commit_list(common);\n      \t\tfree_commit_list(remotes);\n12:  3814f61717 ! 13:  7eb0f13442 sequencer: use the \"octopus\" merge strategy without forking\n    @@ sequencer.c: static int do_pick_commit(struct repository *r,\n     +\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n     +\t\t\trepo_read_index(r);\n     +\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n    - \t\t} else\n    + \t\t} else {\n      \t\t\tres |= try_merge_command(r, opts->strategy,\n      \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410659","messageId":"20201124115315.13311-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 02/13] t6060: modify multiple files to expose a possible issue with merge-index","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:04Z","receivedAt":"2020-11-24T11:55:07Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Currently, merge-index iterates over every index entry, skipping stage0\nentries.  It will then count how many entries following the current one\nhave the same name, then fork to do the merge.  It will then increase\nthe iterator by the number of entries to skip them.  This behaviour is\ncorrect, as even if the subprocess modifies the index, merge-index does\nnot reload it at all.\n\nBut when it will be rewritten to use a function, the index it will use\nwill be modified and may shrink when a conflict happens or if a file is\nremoved, so we have to be careful to handle such cases.\n\nHere is an example:\n\n *    Merge branches, file1 and file2 are trivially mergeable.\n |\\\n | *  Modifies file1 and file2.\n * |  Modifies file1 and file2.\n |/\n *    Adds file1 and file2.\n\nWhen the merge happens, the index will look like that:\n\n i -> 0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n      3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\nmerge-index handles `file1' first.  As it appears 3 times after the\niterator, it is merged.  The index is now stale, `i' is increased by 3,\nand the index now looks like this:\n\n      0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n i -> 3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\n`file2' appears three times too, so it is merged.\n\nWith a naive rewrite, the index would look like this:\n\n      0. file1 (stage0)\n      1. file2 (stage1)\n      2. file2 (stage2)\n i -> 3. file2 (stage3)\n\n`file2' appears once at the iterator or after, so it will be added,\n_not_ merged.  Which is wrong.\n\nA naive rewrite would lead to unproperly merged files, or even files not\nhandled at all.\n\nThis changes t6060 to reproduce this case, by creating 2 files instead\nof 1, to check the correctness of the soon-to-be-rewritten merge-index.\nThe files are identical, which is not really important -- the factors\nthat could trigger this issue are that they should be separated by at\nmost one entry in the index, and that the first one in the index should\nbe trivially mergeable.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6060-merge-index.sh | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex ddf34f0115..9e15ceb957 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -7,16 +7,19 @@ test_expect_success 'setup diverging branches' '\n \tfor i in 1 2 3 4 5 6 7 8 9 10; do\n \t\techo $i\n \tdone >file &&\n-\tgit add file &&\n+\tcp file file2 &&\n+\tgit add file file2 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n \tsed s/10/ten/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m ten &&\n \tgit tag ten\n '\n@@ -35,8 +38,11 @@ ten\n EOF\n \n test_expect_success 'read-tree does not resolve content merge' '\n+\tcat >expect <<-\\EOF &&\n+\tfile\n+\tfile2\n+\tEOF\n \tgit read-tree -i -m base ten two &&\n-\techo file >expect &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_cmp expect unmerged\n '\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410660","messageId":"20201124115315.13311-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 03/13] update-index: move add_cacheinfo() to read-cache.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:05Z","receivedAt":"2020-11-24T11:55:07Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves the function add_cacheinfo() that already exists in\nupdate-index.c to update-index.c, renames it add_to_index_cacheinfo(),\nand adds an `istate' parameter.  The new cache entry is returned through\na pointer passed in the parameters.  The return value is either 0\n(success), -1 (invalid path), or -2 (failed to add the file in the\nindex).\n\nThis will become useful in the next commit, when the three-way merge\nwill need to call this function.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/update-index.c | 25 +++++++------------------\n cache.h                |  5 +++++\n read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n 3 files changed, 47 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 79087bccea..44862f5e1d 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -404,27 +404,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n \t\t\t const char *path, int stage)\n {\n-\tint len, option;\n-\tstruct cache_entry *ce;\n+\tint res;\n \n-\tif (!verify_path(path, mode))\n-\t\treturn error(\"Invalid path '%s'\", path);\n-\n-\tlen = strlen(path);\n-\tce = make_empty_cache_entry(&the_index, len);\n-\n-\toidcpy(&ce->oid, oid);\n-\tmemcpy(ce->name, path, len);\n-\tce->ce_flags = create_ce_flags(stage);\n-\tce->ce_namelen = len;\n-\tce->ce_mode = create_ce_mode(mode);\n-\tif (assume_unchanged)\n-\t\tce->ce_flags |= CE_VALID;\n-\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n-\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n-\tif (add_cache_entry(ce, option))\n+\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n+\t\t\t\t     allow_add, allow_replace, NULL);\n+\tif (res == -1)\n+\t\treturn res;\n+\tif (res == -2)\n \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n \t\t\t     path);\n+\n \treport(\"add '%s'\", path);\n \treturn 0;\n }\ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..be16ab3215 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -830,6 +830,11 @@ int remove_file_from_index(struct index_state *, const char *path);\n int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n int add_file_to_index(struct index_state *, const char *path, int flags);\n \n+int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **pce);\n+\n int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\ndiff --git a/read-cache.c b/read-cache.c\nindex ecf6f68994..c25f951db4 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1350,6 +1350,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n \treturn 0;\n }\n \n+int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **pce)\n+{\n+\tint len, option;\n+\tstruct cache_entry *ce = NULL;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(stage);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n+\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n+\n+\tif (add_index_entry(istate, ce, option)) {\n+\t\tdiscard_cache_entry(ce);\n+\t\treturn -2;\n+\t}\n+\n+\tif (pce)\n+\t\t*pce = ce;\n+\n+\treturn 0;\n+}\n+\n /*\n  * \"refresh\" does not calculate a new sha1 file or bring the\n  * cache up-to-date for mode/content changes. But what it\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410664","messageId":"20201124115315.13311-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 04/13] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:06Z","receivedAt":"2020-11-24T11:55:07Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_to_index_cacheinfo();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_index();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and xdl_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are removed, as this is needed\n   to have the correct permission bits on the merged file from the\n   script, but not in the C version;\n\n - calls to `update-index' are replaced by calls to add_file_to_index().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   3 +-\n builtin.h                       |   1 +\n builtin/merge-one-file.c        |  94 +++++++++++++++++\n git-merge-one-file.sh           | 167 ------------------------------\n git.c                           |   1 +\n merge-strategies.c              | 178 ++++++++++++++++++++++++++++++++\n merge-strategies.h              |  12 +++\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 8 files changed, 289 insertions(+), 169 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex de53954590..6dfdb33cb2 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -909,6 +908,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\n@@ -1094,6 +1094,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..4d2cd78856 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -178,6 +178,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..9c21778e1d\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,94 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file object name (or empty)\n+ *   argv[2] - file in branch1 object name (or empty)\n+ *   argv[3] - file in branch2 object name (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+static int read_mode(const char *name, const char *arg, unsigned int *mode)\n+{\n+\tchar *last;\n+\tint ret = 0;\n+\n+\t*mode = strtol(arg, &last, 8);\n+\n+\tif (*last)\n+\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n+\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n+\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n+\n+\treturn ret;\n+}\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (!get_oid_hex(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n+\t} else if (!*argv[1] && *argv[5])\n+\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tret = read_mode(\"our\", argv[6], &our_mode);\n+\t} else if (!*argv[2] && *argv[6])\n+\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\tret = read_mode(\"their\", argv[7], &their_mode);\n+\t} else if (!*argv[3] && *argv[7])\n+\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_three_way(the_repository, p_orig_blob, p_our_blob, p_their_blob,\n+\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex f1e8b56d99..a4d3f98094 100644\n--- a/git.c\n+++ b/git.c\n@@ -540,6 +540,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..20a328bf57\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,178 @@\n+#include \"cache.h\"\n+#include \"dir.h\"\n+#include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int checkout_from_index(struct index_state *istate, const char *path,\n+\t\t\t       struct cache_entry *ce)\n+{\n+\tstruct checkout state = CHECKOUT_INIT;\n+\n+\tstate.istate = istate;\n+\tstate.force = 1;\n+\tstate.base_dir = \"\";\n+\tstate.base_dir_len = 0;\n+\n+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((our_blob && orig_mode != our_mode) ||\n+\t    (their_blob && orig_mode != their_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tssize_t written;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, &null_oid);\n+\t}\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n+\n+\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"Failed to execute internal merge\"));\n+\t}\n+\n+\tif (ret > 0 || !orig_blob)\n+\t\tret = error(_(\"content conflict in %s\"), path);\n+\tif (our_mode != their_mode)\n+\t\tret = error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t    orig_mode, our_mode, their_mode, path);\n+\n+\tunlink(path);\n+\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n+\t}\n+\n+\twritten = write_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (written < 0)\n+\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_three_way(struct repository *r,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n+\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n+\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\t} else if (!orig_blob && our_blob && !their_blob) {\n+\t\t/*\n+\t\t * Added in one.  The other side did not add and we\n+\t\t * added so there is nothing to be done, except making\n+\t\t * the path merged.\n+\t\t */\n+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob,\n+\t\t\t\t\t      path, 0, 1, 1, NULL);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tstruct cache_entry *ce;\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob,\n+\t\t\t\t\t   path, 0, 1, 1, &ce))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path, ce);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\tstruct cache_entry *ce;\n+\n+\t\t/* Added in both, identically (check for same permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob,\n+\t\t\t\t\t   path, 0, 1, 1, &ce))\n+\t\t\treturn -1;\n+\t\treturn checkout_from_index(r->index, path, ce);\n+\t} else if (our_blob && their_blob) {\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(r->index,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\t} else {\n+\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n+\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n+\n+\t\tif (orig_blob)\n+\t\t\toid_to_hex_r(orig_hex, orig_blob);\n+\t\tif (our_blob)\n+\t\t\toid_to_hex_r(our_hex, our_blob);\n+\t\tif (their_blob)\n+\t\t\toid_to_hex_r(their_hex, their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..e624c4f27c\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,12 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+int merge_three_way(struct repository *r,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n+\n+#endif /* MERGE_STRATEGIES_H */\ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2eddcc7664..5fb74e39a0 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410661","messageId":"20201124115315.13311-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 07/13] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:09Z","receivedAt":"2020-11-24T11:55:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all_index() function.\n\nThe index is read in cmd_merge_resolve(), and is wrote back by\nmerge_strategies_resolve().\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |  2 +-\n builtin.h               |  1 +\n builtin/merge-resolve.c | 73 +++++++++++++++++++++++++++++++\n git-merge-resolve.sh    | 54 -----------------------\n git.c                   |  1 +\n merge-strategies.c      | 95 +++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 7 files changed, 176 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex 6dfdb33cb2..3cc6b192f1 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1097,6 +1096,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 4d2cd78856..35e91c16d0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -180,6 +180,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..dca31676b8\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,73 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--\"))\n+\t\t\tsep_seen = 1;\n+\t\telse if (!strcmp(argv[i], \"-h\"))\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tcommit_list_insert(commit, &remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Give up if we are given two or more remotes.  Not handling\n+\t * octopus.\n+\t */\n+\tif (remote && remote->next)\n+\t\treturn 2;\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (!bases)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 343fe7bccd..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex a4d3f98094..64a1a1de41 100644\n--- a/git.c\n+++ b/git.c\n@@ -544,6 +544,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 542cefcf3d..9aa07e91b5 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,7 +1,10 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int checkout_from_index(struct index_state *istate, const char *path,\n@@ -297,3 +300,95 @@ int merge_all_index(struct repository *r, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int fast_forward(struct repository *r, struct tree_desc *t,\n+\t\t\tint nr, int aggressive)\n+{\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int add_tree(struct tree *tree, struct tree_desc *t)\n+{\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct object_id head, oid;\n+\tstruct commit_list *i;\n+\tint nr = 0;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\n+\tfor (i = bases; i && i->item; i = i->next) {\n+\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (head_arg) {\n+\t\tstruct tree *tree = parse_tree_indirect(&head);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n+\t\treturn 2;\n+\n+\tif (fast_forward(r, t, nr, 1))\n+\t\treturn 2;\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all_index(r, 1, 0, merge_one_file_func, NULL);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 0b74d45431..47dcd71ad5 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_three_way(struct repository *r,\n@@ -35,4 +36,8 @@ int merge_index_path(struct repository *r, int oneshot, int quiet,\n int merge_all_index(struct repository *r, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410662","messageId":"20201124115315.13311-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 05/13] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:07Z","receivedAt":"2020-11-24T11:55:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves and renames merge_one_path(), merge_all(), and\ntheir helpers to merge-strategies.c.  They also take a callback to\ndictate what they should do for each file.  For now, to preserve the\nbehaviour of `merge-index', only one callback, launching a new process,\nis defined.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c |  77 +++----------------------------\n merge-strategies.c    | 104 ++++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    |  19 ++++++++\n 3 files changed, 130 insertions(+), 70 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..d5e5713b25 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,11 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n-#include \"run-command.h\"\n-\n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n-\n-static int merge_entry(int pos, const char *path)\n-{\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n-\t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n-\n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n-\n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n-\t}\n-}\n+#include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tconst char *pgm;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all_index(the_repository, one_shot, quiet,\n+\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_index_path(the_repository, one_shot, quiet, arg,\n+\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 20a328bf57..6f27e66dfe 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,6 +1,7 @@\n #include \"cache.h\"\n #include \"dir.h\"\n #include \"merge-strategies.h\"\n+#include \"run-command.h\"\n #include \"xdiff-interface.h\"\n \n static int checkout_from_index(struct index_state *istate, const char *path,\n@@ -176,3 +177,106 @@ int merge_three_way(struct repository *r,\n \n \treturn 0;\n }\n+\n+int merge_one_file_spawn(struct repository *r,\n+\t\t\t const struct object_id *orig_blob,\n+\t\t\t const struct object_id *our_blob,\n+\t\t\t const struct object_id *their_blob, const char *path,\n+\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t void *data)\n+{\n+\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n+\tchar modes[3][10] = {{0}};\n+\tconst char *arguments[] = { (char *)data, oids[0], oids[1], oids[2],\n+\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n+\n+\tif (orig_blob) {\n+\t\toid_to_hex_r(oids[0], orig_blob);\n+\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n+\t}\n+\n+\tif (our_blob) {\n+\t\toid_to_hex_r(oids[1], our_blob);\n+\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n+\t}\n+\n+\tif (their_blob) {\n+\t\toid_to_hex_r(oids[2], their_blob);\n+\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n+\t}\n+\n+\treturn run_command_v_opt(arguments, 0);\n+}\n+\n+static int merge_entry(struct repository *r, int quiet, unsigned int pos,\n+\t\t       const char *path, int *err, merge_fn fn, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = r->index->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < r->index->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (fn(r, oids[0], oids[1], oids[2], path,\n+\t       modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\t(*err)++;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct repository *r, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data)\n+{\n+\tint pos = index_name_pos(r->index, path, strlen(path)), ret, err = 0;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(r, quiet || oneshot, -pos - 1, path, &err, fn, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (err)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct repository *r, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data)\n+{\n+\tint err = 0, ret;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i < r->index->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = r->index->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\n+\t\tif (err && !oneshot)\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex e624c4f27c..94c40635c4 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -9,4 +9,23 @@ int merge_three_way(struct repository *r,\n \t\t    const struct object_id *their_blob, const char *path,\n \t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n \n+typedef int (*merge_fn)(struct repository *r,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_one_file_spawn(struct repository *r,\n+\t\t\t const struct object_id *orig_blob,\n+\t\t\t const struct object_id *our_blob,\n+\t\t\t const struct object_id *their_blob, const char *path,\n+\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t void *data);\n+\n+int merge_index_path(struct repository *r, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data);\n+int merge_all_index(struct repository *r, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410663","messageId":"20201124115315.13311-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:08Z","receivedAt":"2020-11-24T11:55:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' has been rewritten and libified, this teaches\n`merge-index' to call merge_three_way() without forking using a new\ncallback, merge_one_file_func().\n\nTo avoid any issue with a shrinking index because of the merge function\nused (directly in the process or by forking), as described earlier, the\niterator of the loop of merge_all_index() is increased by the number of\nentries with the same name, minus the difference between the number of\nentries in the index before and after the merge.\n\nThis should handle a shrinking index correctly, but could lead to issues\nwith a growing index.  However, this case is not treated, as there is no\ncallback that can produce such a case.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 28 ++++++++++++++++++++++++++--\n merge-strategies.c    | 25 +++++++++++++++++++++----\n merge-strategies.h    |  7 +++++++\n 3 files changed, 54 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex d5e5713b25..60fcde579f 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,11 +1,15 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \tconst char *pgm;\n+\tvoid *data = NULL;\n+\tmerge_fn merge_action;\n+\tstruct lock_file lock = LOCK_INIT;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -26,7 +30,18 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\tsetup_work_tree();\n+\n+\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n+\t\tmerge_action = merge_one_file_func;\n+\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\t} else {\n+\t\tmerge_action = merge_one_file_spawn;\n+\t\tdata = (void *)pgm;\n+\t}\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -36,13 +51,22 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all_index(the_repository, one_shot, quiet,\n-\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n+\t\t\t\t\t\t       merge_action, data);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_index_path(the_repository, one_shot, quiet, arg,\n-\t\t\t\t\tmerge_one_file_spawn, (void *)pgm);\n+\t\t\t\t\tmerge_action, data);\n+\t}\n+\n+\tif (merge_action == merge_one_file_func) {\n+\t\tif (err) {\n+\t\t\trollback_lock_file(&lock);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n \t}\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 6f27e66dfe..542cefcf3d 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -178,6 +178,18 @@ int merge_three_way(struct repository *r,\n \treturn 0;\n }\n \n+int merge_one_file_func(struct repository *r,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data)\n+{\n+\treturn merge_three_way(r,\n+\t\t\t       orig_blob, our_blob, their_blob, path,\n+\t\t\t       orig_mode, our_mode, their_mode);\n+}\n+\n int merge_one_file_spawn(struct repository *r,\n \t\t\t const struct object_id *orig_blob,\n \t\t\t const struct object_id *our_blob,\n@@ -261,17 +273,22 @@ int merge_all_index(struct repository *r, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data)\n {\n \tint err = 0, ret;\n-\tunsigned int i;\n+\tunsigned int i, prev_nr;\n \n \tfor (i = 0; i < r->index->cache_nr; i++) {\n \t\tconst struct cache_entry *ce = r->index->cache[i];\n \t\tif (!ce_stage(ce))\n \t\t\tcontinue;\n \n+\t\tprev_nr = r->index->cache_nr;\n \t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n-\t\tif (ret > 0)\n-\t\t\ti += ret - 1;\n-\t\telse if (ret == -1)\n+\t\tif (ret > 0) {\n+\t\t\t/* Don't bother handling an index that has\n+\t\t\t   grown, since merge_one_file_func() can't grow\n+\t\t\t   it, and merge_one_file_spawn() can't change\n+\t\t\t   it. */\n+\t\t\ti += ret - (prev_nr - r->index->cache_nr) - 1;\n+\t\t} else if (ret == -1)\n \t\t\treturn -1;\n \n \t\tif (err && !oneshot)\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 94c40635c4..0b74d45431 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -16,6 +16,13 @@ typedef int (*merge_fn)(struct repository *r,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_func(struct repository *r,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n int merge_one_file_spawn(struct repository *r,\n \t\t\t const struct object_id *orig_blob,\n \t\t\t const struct object_id *our_blob,\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410665","messageId":"20201124115315.13311-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 08/13] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:10Z","receivedAt":"2020-11-24T11:55:38Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"better_branch_name() will be used by merge-octopus once it is rewritten\nin C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex be16ab3215..2d844576ea 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1933,7 +1933,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 5fb88af102..801d673c5f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410666","messageId":"20201124115315.13311-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 09/13] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:11Z","receivedAt":"2020-11-24T11:55:38Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all_index().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_strategies_octopus().\n\nHere to, merge_strategies_octopus() takes two commit lists and a string\nto reduce frictions when try_merge_strategies() will be modified to call\nit directly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  69 ++++++++++++++++\n git-merge-octopus.sh    | 112 -------------------------\n git.c                   |   1 +\n merge-strategies.c      | 177 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 252 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 3cc6b192f1..2b2bdffafe 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -600,7 +600,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1093,6 +1092,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 35e91c16d0..50225404a0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -176,6 +176,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..ca8f9f345d\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,69 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (read_cache() < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Reject if this is not an octopus -- resolve should be used\n+\t * instead.\n+\t */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 7d19d37951..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 64a1a1de41..d51fb5d2bf 100644\n--- a/git.c\n+++ b/git.c\n@@ -539,6 +539,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 9aa07e91b5..4d9dd55296 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"lockfile.h\"\n #include \"merge-strategies.h\"\n@@ -392,3 +393,179 @@ int merge_strategies_resolve(struct repository *r,\n \n \treturn 0;\n }\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\t\tWRITE_TREE_SILENT, NULL)))\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+static int octopus_fast_forward(struct repository *r, const char *branch_name,\n+\t\t\t\tstruct tree *tree_head, struct tree *current_tree,\n+\t\t\t\tstruct tree **reference_tree)\n+{\n+\t/*\n+\t * The first head being merged was a fast-forward.  Advance the\n+\t * reference commit to the head being merged, and use that tree\n+\t * as the intermediate result of the merge.  We still need to\n+\t * count this as part of the parent set.\n+\t */\n+\tstruct tree_desc t[2];\n+\n+\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n+\tif (add_tree(current_tree, t + 1))\n+\t\treturn -1;\n+\tif (fast_forward(r, t, 2, 0))\n+\t\treturn -1;\n+\tif (write_tree(r, reference_tree))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int octopus_do_merge(struct repository *r, const char *branch_name,\n+\t\t\t    struct commit_list *common, struct tree *current_tree,\n+\t\t\t    struct tree **reference_tree)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct commit_list *j;\n+\tint nr = 0, ret = 0;\n+\n+\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\tfor (j = common; j; j = j->next) {\n+\t\tstruct tree *tree = repo_get_commit_tree(r, j->item);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn -1;\n+\t}\n+\n+\tif (add_tree(*reference_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (add_tree(current_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (fast_forward(r, t, nr, 1))\n+\t\treturn -1;\n+\n+\tif (write_tree(r, reference_tree)) {\n+\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all_index(r, 1, 0, merge_one_file_func, NULL);\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\twrite_tree(r, reference_tree);\n+\t}\n+\n+\treturn ret ? -2 : 0;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint ff_merge = 1, ret = 0, references = 1;\n+\tstruct commit **reference_commit, *head_commit;\n+\tstruct tree *reference_tree, *head_tree;\n+\tstruct commit_list *i;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\thead_commit = lookup_commit_reference(r, &head);\n+\thead_tree = repo_get_commit_tree(r, head_commit);\n+\n+\tif (parse_tree(head_tree))\n+\t\treturn 2;\n+\n+\tif (repo_index_has_changes(r, head_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn 2;\n+\t}\n+\n+\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n+\t\t\t\t   sizeof(struct commit *));\n+\treference_commit[0] = head_commit;\n+\treference_tree = head_tree;\n+\n+\tfor (i = remotes; i && i->item; i = i->next) {\n+\t\tstruct commit *c = i->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n+\t\tstruct commit_list *common, *j;\n+\t\tchar *branch_name;\n+\t\tint k = 0, up_to_date = 0;\n+\n+\t\tif (ret) {\n+\t\t\t/*\n+\t\t\t * We allow only last one to have a\n+\t\t\t * hand-resolvable conflicts.  Last round failed\n+\t\t\t * and we still had a head to merge.\n+\t\t\t */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tfree(reference_commit);\n+\t\t\treturn 2;\n+\t\t}\n+\n+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n+\n+\t\tif (!common) {\n+\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tfree(reference_commit);\n+\n+\t\t\treturn 2;\n+\t\t}\n+\n+\t\tfor (j = common; j && !(up_to_date || !ff_merge); j = j->next) {\n+\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n+\n+\t\t\tif (k < references)\n+\t\t\t\tff_merge &= oideq(&j->item->object.oid, &reference_commit[k++]->object.oid);\n+\t\t}\n+\n+\t\tif (up_to_date) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (ff_merge) {\n+\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n+\t\t\t\t\t\t   current_tree, &reference_tree);\n+\t\t\treferences = 0;\n+\t\t} else {\n+\t\t\tret = octopus_do_merge(r, branch_name, common,\n+\t\t\t\t\t       current_tree, &reference_tree);\n+\t\t}\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\n+\t\tif (ret == -1)\n+\t\t\tbreak;\n+\n+\t\treference_commit[references++] = c;\n+\t}\n+\n+\tfree(reference_commit);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 47dcd71ad5..05c50159ec 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -39,5 +39,8 @@ int merge_all_index(struct repository *r, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410668","messageId":"20201124115315.13311-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 11/13] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:13Z","receivedAt":"2020-11-24T11:55:38Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 3b35aa320c..f3345a582a 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -744,6 +744,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\")) {\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\t} else if (!strcmp(strategy, \"octopus\")) {\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410667","messageId":"20201124115315.13311-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 10/13] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:12Z","receivedAt":"2020-11-24T11:55:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..3b35aa320c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -41,6 +41,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -740,6 +741,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n+\t} else if (!strcmp(strategy, \"resolve\")) {\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410669","messageId":"20201124115315.13311-14-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 13/13] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:15Z","receivedAt":"2020-11-24T11:55:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 706c2eee87..591de451a2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2005,6 +2005,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else {\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410670","messageId":"20201124115315.13311-13-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v6 12/13] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-11-24T11:53:14Z","receivedAt":"2020-11-24T11:55:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 14 +++++++++++---\n 1 file changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e8676e965f..706c2eee87 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -33,6 +33,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2000,9 +2001,16 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else {\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\t\t}\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.29.2.260.ge31aba42fb\n\n"},{"id":"410695","messageId":"20201124193417.GD8396@szeder.dev","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"Re: [PATCH v6 00/13] Rewrite the remaining merge strategies from shell to C","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2020-11-24T19:34:17Z","receivedAt":"2020-11-24T19:34:21Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Nov 24, 2020 at 12:53:02PM +0100, Alban Gruin wrote:\n> In a effort to reduce the number of shell scripts in git's codebase, I\n> propose this patch series converting the two remaining merge strategies,\n> resolve and octopus, from shell to C.  This will enable slightly better\n> performance, better integration with git itself (no more forking to\n> perform these operations), better portability (Windows and shell scripts\n> don't mix well).\n> \n> Three scripts are actually converted: first git-merge-one-file.sh, then\n> git-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\n> are converted, but they also are modified to operate without forking,\n> and then libified so they can be used by git without spawning another\n> process.\n\n> This series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).\n\nThis patch series should be based on top of 722fc37491 (help: do not\nexpect built-in commands to be hardlinked, 2020-10-07) (in\nv2.29.0-rc1), because without the fix in that commit we don't get the\nlist of available merge strategies when building with\nSKIP_DASHED_BUILT_INS=YesPlease:\n\n  $ make clean\n  [...]\n  $ SKIP_DASHED_BUILT_INS=YesPlease make\n  [...]\n  $ git merge -s help\n  Could not find merge strategy 'help'.\n  Available strategies are:.\n\nOur completion script relies on this to list available strategies, and\na test in 't9902-completion.sh' fails without that fix.\n\n"},{"id":"412858","messageId":"xmqqczz1h88v.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201124115315.13311-4-alban.gruin@gmail.com","subject":"Re: [PATCH v6 03/13] update-index: move add_cacheinfo() to read-cache.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-22T20:54:24Z","receivedAt":"2020-12-22T20:55:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> This moves the function add_cacheinfo() that already exists in\n> update-index.c to update-index.c, renames it add_to_index_cacheinfo(),\n> and adds an `istate' parameter.  The new cache entry is returned through\n> a pointer passed in the parameters.  The return value is either 0\n> (success), -1 (invalid path), or -2 (failed to add the file in the\n> index).\n>\n> This will become useful in the next commit, when the three-way merge\n> will need to call this function.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  builtin/update-index.c | 25 +++++++------------------\n>  cache.h                |  5 +++++\n>  read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n>  3 files changed, 47 insertions(+), 18 deletions(-)\n>\n> diff --git a/builtin/update-index.c b/builtin/update-index.c\n> index 79087bccea..44862f5e1d 100644\n> --- a/builtin/update-index.c\n> +++ b/builtin/update-index.c\n> @@ -404,27 +404,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n>  static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n>  \t\t\t const char *path, int stage)\n>  {\n> -\tint len, option;\n> -\tstruct cache_entry *ce;\n> +\tint res;\n>  \n> -\tif (!verify_path(path, mode))\n> -\t\treturn error(\"Invalid path '%s'\", path);\n> -\n> -\tlen = strlen(path);\n> -\tce = make_empty_cache_entry(&the_index, len);\n> -\n> -\toidcpy(&ce->oid, oid);\n> -\tmemcpy(ce->name, path, len);\n> -\tce->ce_flags = create_ce_flags(stage);\n> -\tce->ce_namelen = len;\n> -\tce->ce_mode = create_ce_mode(mode);\n> -\tif (assume_unchanged)\n> -\t\tce->ce_flags |= CE_VALID;\n> -\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n> -\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n> -\tif (add_cache_entry(ce, option))\n> +\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n> +\t\t\t\t     allow_add, allow_replace, NULL);\n> +\tif (res == -1)\n> +\t\treturn res;\n> +\tif (res == -2)\n>  \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n>  \t\t\t     path);\n\nIntroduce a symbolic constant (C preprocessor macros) so that the\nabove becomes\n\n\tif (res == ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD)\n\t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n\t\t\t     path);\n\tif (res < 0)\n\t\treturn res;\n\nor something like that.\n\nStepping back a bit.\n\nIt feels _really_ odd that add_to_index_cacheinfo() became silent\nonly for one error-return case while the other error case emits an\nerror message on its own, without any way to squelch it.  Isn't this\nadapting too much the need of a single (future) caller?\n\nIt may make more sense to do\n\n\t#define ADD_TO_INDEX_CACHEINFO_INVALID_PATH\t(-1)\n\t#define ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD\t(-2)\n\nand make both silent.  At least that would be more consistent.\n\n> +\n>  \treport(\"add '%s'\", path);\n>  \treturn 0;\n>  }\n> diff --git a/cache.h b/cache.h\n> index c0072d43b1..be16ab3215 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -830,6 +830,11 @@ int remove_file_from_index(struct index_state *, const char *path);\n>  int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n>  int add_file_to_index(struct index_state *, const char *path, int flags);\n\nAs a public function with mysterious 0/-1/-2 return values, a reader\ndeserves to see a comment to understand how to call this function,\nhow to treat its return value, etc.\n\nYou already have enough material to fill in such a comment in your\nproposed log message, it seems, which is good.\n\n> +int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n> +\t\t\t   const struct object_id *oid, const char *path,\n> +\t\t\t   int stage, int allow_add, int allow_replace,\n> +\t\t\t   struct cache_entry **pce);\n> +\n>  int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n>  int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n>  void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\n> diff --git a/read-cache.c b/read-cache.c\n> index ecf6f68994..c25f951db4 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -1350,6 +1350,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n>  \treturn 0;\n>  }\n>  \n> +int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n> +\t\t\t   const struct object_id *oid, const char *path,\n> +\t\t\t   int stage, int allow_add, int allow_replace,\n> +\t\t\t   struct cache_entry **pce)\n> +{\n\nI see two behaviour differences from the original, which may be\nworth noting in the proposed log message as difference.\n\n - callers of add_cacheinfo() never learned of the new cache entry;\n   this allows the caller to optionally obtain a pointer to it.\n\n - we used to leak a new cache entry when add_cache_entry() refused\n   to add it to the index; the leak got plugged.\n\n> +\tint len, option;\n> +\tstruct cache_entry *ce = NULL;\n\nWhy initialize it to NULL?  It is quite clear in the code that the\nvariable is never used until it is assigned to.\n\n> +\tif (!verify_path(path, mode))\n> +\t\treturn error(_(\"Invalid path '%s'\"), path);\n> +\n> +\tlen = strlen(path);\n> +\tce = make_empty_cache_entry(istate, len);\n> +\n> +\toidcpy(&ce->oid, oid);\n> +\tmemcpy(ce->name, path, len);\n> +\tce->ce_flags = create_ce_flags(stage);\n> +\tce->ce_namelen = len;\n> +\tce->ce_mode = create_ce_mode(mode);\n> +\tif (assume_unchanged)\n> +\t\tce->ce_flags |= CE_VALID;\n> +\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n> +\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n> +\n> +\tif (add_index_entry(istate, ce, option)) {\n> +\t\tdiscard_cache_entry(ce);\n\nThis behaviour is new.  We were leaking the ce.\n\n> +\t\treturn -2;\n> +\t}\n> +\n> +\tif (pce)\n> +\t\t*pce = ce;\n\nI think you mean by 'p' a \"pointer\", but that is a horrible way to\nname things.  We know from the type that it is a pointer to a\npointer already; what reader needs to learn from either its name or\na comment associated with it is what purpose it serves.\n\nPerhaps call it with a name that hints it is used as the return\nparameter, e.g. ce_ret?\n\n> +\treturn 0;\n> +}\n> +\n>  /*\n>   * \"refresh\" does not calculate a new sha1 file or bring the\n>   * cache up-to-date for mode/content changes. But what it\n"},{"id":"412859","messageId":"xmqq5z4th6ak.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"20201124115315.13311-5-alban.gruin@gmail.com","subject":"Re: [PATCH v6 04/13] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-22T21:36:35Z","receivedAt":"2020-12-22T21:37:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> This rewrites `git merge-one-file' from shell to C.  This port is not\n> completely straightforward: to save precious cycles by avoiding reading\n> and flushing the index repeatedly, write temporary files when an\n> operation can be performed in-memory, or allow other function to use the\n> rewrite without forking nor worrying about the index, the calls to\n> external processes are replaced by calls to functions in libgit.a:\n>\n>  - calls to `update-index --add --cacheinfo' are replaced by calls to\n>    add_to_index_cacheinfo();\n>\n>  - calls to `update-index --remove' are replaced by calls to\n>    remove_file_from_index();\n>\n>  - calls to `checkout-index -u -f' are replaced by calls to\n>    checkout_entry();\n>\n>  - calls to `unpack-file' and `merge-files' are replaced by calls to\n>    read_mmblob() and xdl_merge(), respectively, to merge files\n>    in-memory;\n>\n>  - calls to `checkout-index -f --stage=2' are removed, as this is needed\n>    to have the correct permission bits on the merged file from the\n>    script, but not in the C version;\n>\n>  - calls to `update-index' are replaced by calls to add_file_to_index().\n>\n> The bulk of the rewrite is done in a new file in libgit.a,\n> merge-strategies.c.  This will enable the resolve and octopus strategies\n> to directly call it instead of forking.\n>\n> This also fixes a bug present in the original script: instead of\n> checking if a _regular_ file exists when a file exists in the branch to\n> merge, but not in our branch, the rewritten version checks if a file of\n> any kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\n> where the branch to merge had a new file, `a/b', but our branch had a\n> directory there; it should have failed because a directory exists, but\n> it did not because there was no regular file called `a/b'.  This test is\n> now marked as successful.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  Makefile                        |   3 +-\n>  builtin.h                       |   1 +\n>  builtin/merge-one-file.c        |  94 +++++++++++++++++\n>  git-merge-one-file.sh           | 167 ------------------------------\n>  git.c                           |   1 +\n>  merge-strategies.c              | 178 ++++++++++++++++++++++++++++++++\n>  merge-strategies.h              |  12 +++\n>  t/t6415-merge-dir-to-symlink.sh |   2 +-\n>  8 files changed, 289 insertions(+), 169 deletions(-)\n>  create mode 100644 builtin/merge-one-file.c\n>  delete mode 100755 git-merge-one-file.sh\n>  create mode 100644 merge-strategies.c\n>  create mode 100644 merge-strategies.h\n>\n> diff --git a/Makefile b/Makefile\n> index de53954590..6dfdb33cb2 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -601,7 +601,6 @@ SCRIPT_SH += git-bisect.sh\n>  SCRIPT_SH += git-difftool--helper.sh\n>  SCRIPT_SH += git-filter-branch.sh\n>  SCRIPT_SH += git-merge-octopus.sh\n> -SCRIPT_SH += git-merge-one-file.sh\n>  SCRIPT_SH += git-merge-resolve.sh\n>  SCRIPT_SH += git-mergetool.sh\n>  SCRIPT_SH += git-quiltimport.sh\n> @@ -909,6 +908,7 @@ LIB_OBJS += match-trees.o\n>  LIB_OBJS += mem-pool.o\n>  LIB_OBJS += merge-blobs.o\n>  LIB_OBJS += merge-recursive.o\n> +LIB_OBJS += merge-strategies.o\n>  LIB_OBJS += merge.o\n>  LIB_OBJS += mergesort.o\n>  LIB_OBJS += midx.o\n> @@ -1094,6 +1094,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n>  BUILTIN_OBJS += builtin/merge-base.o\n>  BUILTIN_OBJS += builtin/merge-file.o\n>  BUILTIN_OBJS += builtin/merge-index.o\n> +BUILTIN_OBJS += builtin/merge-one-file.o\n>  BUILTIN_OBJS += builtin/merge-ours.o\n>  BUILTIN_OBJS += builtin/merge-recursive.o\n>  BUILTIN_OBJS += builtin/merge-tree.o\n> diff --git a/builtin.h b/builtin.h\n> index 53fb290963..4d2cd78856 100644\n> --- a/builtin.h\n> +++ b/builtin.h\n> @@ -178,6 +178,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n>  int cmd_merge_index(int argc, const char **argv, const char *prefix);\n>  int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n>  int cmd_merge_file(int argc, const char **argv, const char *prefix);\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n>  int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n>  int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n>  int cmd_mktag(int argc, const char **argv, const char *prefix);\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..9c21778e1d\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,94 @@\n> +/*\n> + * Builtin \"git merge-one-file\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> + *\n> + * This is the git per-file merge utility, called with\n> + *\n> + *   argv[1] - original file object name (or empty)\n> + *   argv[2] - file in branch1 object name (or empty)\n> + *   argv[3] - file in branch2 object name (or empty)\n> + *   argv[4] - pathname in repository\n> + *   argv[5] - original file mode (or empty)\n> + *   argv[6] - file in branch1 mode (or empty)\n> + *   argv[7] - file in branch2 mode (or empty)\n> + *\n> + * Handle some trivial cases. The _really_ trivial cases have been\n> + * handled already by git read-tree, but that one doesn't do any merges\n> + * that might change the tree layout.\n> + */\n> +\n> +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"lockfile.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_one_file_usage[] =\n> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n> +\t\"Blob ids and modes should be empty for missing files.\";\n> +\n> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n> +{\n> +\tchar *last;\n> +\tint ret = 0;\n> +\n> +\t*mode = strtol(arg, &last, 8);\n> +\n> +\tif (*last)\n> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n> +\n> +\treturn ret;\n> +}\n> +\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct object_id orig_blob, our_blob, their_blob,\n> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\n> +\tif (argc != 8)\n> +\t\tusage(builtin_merge_one_file_usage);\n> +\n> +\tif (read_cache() < 0)\n> +\t\tdie(\"invalid index\");\n> +\n> +\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n> +\n> +\tif (!get_oid_hex(argv[1], &orig_blob)) {\n> +\t\tp_orig_blob = &orig_blob;\n> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n> +\t} else if (!*argv[1] && *argv[5])\n> +\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n> +\n> +\tif (!get_oid_hex(argv[2], &our_blob)) {\n> +\t\tp_our_blob = &our_blob;\n> +\t\tret = read_mode(\"our\", argv[6], &our_mode);\n> +\t} else if (!*argv[2] && *argv[6])\n> +\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n> +\n> +\tif (!get_oid_hex(argv[3], &their_blob)) {\n> +\t\tp_their_blob = &their_blob;\n> +\t\tret = read_mode(\"their\", argv[7], &their_mode);\n> +\t} else if (!*argv[3] && *argv[7])\n> +\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n> +\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = merge_three_way(the_repository, p_orig_blob, p_our_blob, p_their_blob,\n> +\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n> +\n> +\tif (ret) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn !!ret;\n> +\t}\n> +\n> +\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n> +}\n> diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\n> deleted file mode 100755\n> index f6d9852d2f..0000000000\n> --- a/git-merge-one-file.sh\n> +++ /dev/null\n> @@ -1,167 +0,0 @@\n> -#!/bin/sh\n> -#\n> -# Copyright (c) Linus Torvalds, 2005\n> -#\n> -# This is the git per-file merge script, called with\n> -#\n> -#   $1 - original file SHA1 (or empty)\n> -#   $2 - file in branch1 SHA1 (or empty)\n> -#   $3 - file in branch2 SHA1 (or empty)\n> -#   $4 - pathname in repository\n> -#   $5 - original file mode (or empty)\n> -#   $6 - file in branch1 mode (or empty)\n> -#   $7 - file in branch2 mode (or empty)\n> -#\n> -# Handle some trivial cases.. The _really_ trivial cases have\n> -# been handled already by git read-tree, but that one doesn't\n> -# do any merges that might change the tree layout.\n> -\n> -USAGE='<orig blob> <our blob> <their blob> <path>'\n> -USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n> -LONG_USAGE=\"usage: git merge-one-file $USAGE\n> -\n> -Blob ids and modes should be empty for missing files.\"\n> -\n> -SUBDIRECTORY_OK=Yes\n> -. git-sh-setup\n> -cd_to_toplevel\n> -require_work_tree\n> -\n> -if test $# != 7\n> -then\n> -\techo \"$LONG_USAGE\"\n> -\texit 1\n> -fi\n> -\n> -case \"${1:-.}${2:-.}${3:-.}\" in\n> -#\n> -# Deleted in both or deleted in one and unchanged in the other\n> -#\n> -\"$1..\" | \"$1.$1\" | \"$1$1.\")\n> -\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n> -\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n> -\tthen\n> -\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n> -\t\techo \"ERROR: permissions changed on the other.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\n> -\tif test -n \"$2\"\n> -\tthen\n> -\t\techo \"Removing $4\"\n> -\telse\n> -\t\t# read-tree checked that index matches HEAD already,\n> -\t\t# so we know we do not have this path tracked.\n> -\t\t# there may be an unrelated working tree file here,\n> -\t\t# which we should just leave unmolested.  Make sure\n> -\t\t# we do not have it in the index, though.\n> -\t\texec git update-index --remove -- \"$4\"\n> -\tfi\n> -\tif test -f \"$4\"\n> -\tthen\n> -\t\trm -f -- \"$4\" &&\n> -\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n> -\tfi &&\n> -\t\texec git update-index --remove -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Added in one.\n> -#\n> -\".$2.\")\n> -\t# the other side did not add and we added so there is nothing\n> -\t# to be done, except making the path merged.\n> -\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n> -\t;;\n> -\"..$3\")\n> -\techo \"Adding $4\"\n> -\tif test -f \"$4\"\n> -\tthen\n> -\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n> -\t\texec git checkout-index -u -f -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Added in both, identically (check for same permissions).\n> -#\n> -\".$3$2\")\n> -\tif test \"$6\" != \"$7\"\n> -\tthen\n> -\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n> -\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\techo \"Adding $4\"\n> -\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n> -\t\texec git checkout-index -u -f -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Modified in both, but differently.\n> -#\n> -\"$1$2$3\" | \".$2$3\")\n> -\n> -\tcase \",$6,$7,\" in\n> -\t*,120000,*)\n> -\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n> -\t\texit 1\n> -\t\t;;\n> -\t*,160000,*)\n> -\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n> -\t\texit 1\n> -\t\t;;\n> -\tesac\n> -\n> -\tsrc1=$(git unpack-file $2)\n> -\tsrc2=$(git unpack-file $3)\n> -\tcase \"$1\" in\n> -\t'')\n> -\t\techo \"Added $4 in both, but differently.\"\n> -\t\torig=$(git unpack-file $(git hash-object /dev/null))\n> -\t\t;;\n> -\t*)\n> -\t\techo \"Auto-merging $4\"\n> -\t\torig=$(git unpack-file $1)\n> -\t\t;;\n> -\tesac\n> -\n> -\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n> -\tret=$?\n> -\tmsg=\n> -\tif test $ret != 0 || test -z \"$1\"\n> -\tthen\n> -\t\tmsg='content conflict'\n> -\t\tret=1\n> -\tfi\n> -\n> -\t# Create the working tree file, using \"our tree\" version from the\n> -\t# index, and then store the result of the merge.\n> -\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n> -\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n> -\n> -\tif test \"$6\" != \"$7\"\n> -\tthen\n> -\t\tif test -n \"$msg\"\n> -\t\tthen\n> -\t\t\tmsg=\"$msg, \"\n> -\t\tfi\n> -\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n> -\t\tret=1\n> -\tfi\n> -\n> -\tif test $ret != 0\n> -\tthen\n> -\t\techo \"ERROR: $msg in $4\" >&2\n> -\t\texit 1\n> -\tfi\n> -\texec git update-index -- \"$4\"\n> -\t;;\n> -\n> -*)\n> -\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n> -\t;;\n> -esac\n> -exit 1\n> diff --git a/git.c b/git.c\n> index f1e8b56d99..a4d3f98094 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -540,6 +540,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n>  \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n>  \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n> +\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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> diff --git a/merge-strategies.c b/merge-strategies.c\n> new file mode 100644\n> index 0000000000..20a328bf57\n> --- /dev/null\n> +++ b/merge-strategies.c\n> @@ -0,0 +1,178 @@\n> +#include \"cache.h\"\n> +#include \"dir.h\"\n> +#include \"merge-strategies.h\"\n> +#include \"xdiff-interface.h\"\n> +\n> +static int checkout_from_index(struct index_state *istate, const char *path,\n> +\t\t\t       struct cache_entry *ce)\n> +{\n> +\tstruct checkout state = CHECKOUT_INIT;\n> +\n> +\tstate.istate = istate;\n> +\tstate.force = 1;\n> +\tstate.base_dir = \"\";\n> +\tstate.base_dir_len = 0;\n> +\n> +\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n> +\t\treturn error(_(\"%s: cannot checkout file\"), path);\n> +\treturn 0;\n> +}\n> +\n> +static int merge_one_file_deleted(struct index_state *istate,\n> +\t\t\t\t  const struct object_id *our_blob,\n> +\t\t\t\t  const struct object_id *their_blob, const char *path,\n> +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif ((our_blob && orig_mode != our_mode) ||\n> +\t    (their_blob && orig_mode != their_mode))\n> +\t\treturn error(_(\"File %s deleted on one branch but had its \"\n> +\t\t\t       \"permissions changed on the other.\"), path);\n> +\n> +\tif (our_blob) {\n> +\t\tprintf(_(\"Removing %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\tremove_path(path);\n> +\t}\n> +\n> +\tif (remove_file_from_index(istate, path))\n> +\t\treturn error(\"%s: cannot remove from the index\", path);\n> +\treturn 0;\n> +}\n> +\n> +static int do_merge_one_file(struct index_state *istate,\n> +\t\t\t     const struct object_id *orig_blob,\n> +\t\t\t     const struct object_id *our_blob,\n> +\t\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tint ret, i, dest;\n> +\tssize_t written;\n> +\tmmbuffer_t result = {NULL, 0};\n> +\tmmfile_t mmfs[3];\n> +\txmparam_t xmp = {{0}};\n> +\n> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n> +\n> +\tif (orig_blob) {\n> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, orig_blob);\n> +\t} else {\n> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, &null_oid);\n> +\t}\n> +\n> +\tread_mmblob(mmfs + 1, our_blob);\n> +\tread_mmblob(mmfs + 2, their_blob);\n> +\n> +\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n> +\txmp.style = 0;\n> +\txmp.favor = 0;\n> +\n> +\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n> +\n> +\tfor (i = 0; i < 3; i++)\n> +\t\tfree(mmfs[i].ptr);\n> +\n> +\tif (ret < 0) {\n> +\t\tfree(result.ptr);\n> +\t\treturn error(_(\"Failed to execute internal merge\"));\n> +\t}\n> +\n> +\tif (ret > 0 || !orig_blob)\n> +\t\tret = error(_(\"content conflict in %s\"), path);\n> +\tif (our_mode != their_mode)\n> +\t\tret = error(_(\"permission conflict: %o->%o,%o in %s\"),\n> +\t\t\t    orig_mode, our_mode, their_mode, path);\n> +\n> +\tunlink(path);\n> +\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n> +\t\tfree(result.ptr);\n> +\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n> +\t}\n> +\n> +\twritten = write_in_full(dest, result.ptr, result.size);\n> +\tclose(dest);\n> +\n> +\tfree(result.ptr);\n> +\n> +\tif (written < 0)\n> +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\treturn add_file_to_index(istate, path, 0);\n> +}\n> +\n> +int merge_three_way(struct repository *r,\n> +\t\t    const struct object_id *orig_blob,\n> +\t\t    const struct object_id *our_blob,\n> +\t\t    const struct object_id *their_blob, const char *path,\n> +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif (orig_blob &&\n> +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n> +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n> +\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n\nWhen both ours and theirs deleted, by definition orig_blob cannot be\nNULL, so \"orig_blob &&\" part would be true, but the other side that\nrequires either (!their && our) or (!our && their) is true cannot be\nsatisfied.  So it seems that the comment does not match the behaviour.\n\nYou'd need \"(!their_blob && !our_blob) ||\" in the second part?\n\nThis shows lack of test coverage, I think A manual test seems to\ntrigger the \"unhandled case\" error you added:\n\n$ make\n$ ./git-merge-one-file $(git rev-parse :COPYING) \"\" \"\" \\\n\tCOPYING \\\n\t100644 \"\" \"\"\nerror: COPYING: Not handling case 536e55524db72bd2acf175208aef4f3dfc148d42 ->  ->\n\n> +\t} else if (!orig_blob && our_blob && !their_blob) {\n> +\t\t/*\n> +\t\t * Added in one.  The other side did not add and we\n> +\t\t * added so there is nothing to be done, except making\n> +\t\t * the path merged.\n> +\t\t */\n\nThis is not the sole \"Added in one\" case.  The next elseif arm also\nis added in one.\n\nWhat is notable in this elseif arm is that this is \"added in ours\",\nwhich allows us (and forces us) not to touch the working tree with\nextra \"checkout\".  So either remove \"Added in one\" from here for\nsymmetry with the next elseif arm, or better yet say \"Added in\nours\".\n\n> +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob,\n> +\t\t\t\t\t      path, 0, 1, 1, NULL);\n\nAll callers to add_to_index_cacheinfo() uses 0, 1, 1 for stage,\nallow_add and allow_replace, except for the original.  The new\ncallers you added should not have to keep repeating 0, 1, 1 like\nthis caller does (see below).\n\n> +\t} else if (!orig_blob && !our_blob && their_blob) {\n> +\t\tstruct cache_entry *ce;\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n> +\n> +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob,\n> +\t\t\t\t\t   path, 0, 1, 1, &ce))\n> +\t\t\treturn -1;\n> +\t\treturn checkout_from_index(r->index, path, ce);\n\n\"git grep -A4 -e add_to_index_cacheinfo\" after applying all patches\nin the series shows us that the &ce parameter was added only to call\ncheckout_from_index() using it.\n\nI doubt add_to_index_cacheinfo() is the right interface for this\nseries.  This caller (and all other callers in the series that calls\nadd_to_index_cacheinfo(), followed by checkout_from_index()) rather\nwants to have a function (defined in <cache.h>):\n\n\textern int add_merge_result_to_index(struct index_state, *\n\t\t\tunsigned int mode,\n                        const struct object_id *oid,\n\t\t\tconst char *path,\n\t\t\tint checkout);\n\nwith which the last 4 lines of the above hunk can just become\n\n\t\treturn add_merge_result_to_index(r->index,\n\t\t\ttheir_mode, their_blob, path, 1);\n\nI would think.  The earlier caller to add_to_index_cacheinfo() for\n\"ours is the result\" can pass 0 to the checkout parameter so the\nhelper won't make a call to checkout_from_index().\n\nAnd the step to add that helper would be in this patch (it could be\nafter the previous step and before this step, but it is probably\neasier to understand if the new helper is introduced with its\ncallers).\n\nIf we were to do that, then I do not mind the repetition of 0, 1, 1\ntoo much.\n\n> +\t} else if (!orig_blob && our_blob && their_blob &&\n> +\t\t   oideq(our_blob, their_blob)) {\n> +\t\tstruct cache_entry *ce;\n> +\n> +\t\t/* Added in both, identically (check for same permissions). */\n> +\t\tif (our_mode != their_mode)\n> +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n> +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n> +\t\t\t\t     path, our_mode, their_mode);\n> +\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob,\n> +\t\t\t\t\t   path, 0, 1, 1, &ce))\n> +\t\t\treturn -1;\n> +\t\treturn checkout_from_index(r->index, path, ce);\n\nLikewise; this wants to call add_merge_result_to_index(), too.\n\n> +\t} else if (our_blob && their_blob) {\n> +\t\t/* Modified in both, but differently. */\n> +\t\treturn do_merge_one_file(r->index,\n> +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n> +\t} else {\n> +\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n> +\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n> +\n> +\t\tif (orig_blob)\n> +\t\t\toid_to_hex_r(orig_hex, orig_blob);\n> +\t\tif (our_blob)\n> +\t\t\toid_to_hex_r(our_hex, our_blob);\n> +\t\tif (their_blob)\n> +\t\t\toid_to_hex_r(their_hex, their_blob);\n> +\n> +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n> +\t\t\t     path, orig_hex, our_hex, their_hex);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> new file mode 100644\n> index 0000000000..e624c4f27c\n> --- /dev/null\n> +++ b/merge-strategies.h\n> @@ -0,0 +1,12 @@\n> +#ifndef MERGE_STRATEGIES_H\n> +#define MERGE_STRATEGIES_H\n> +\n> +#include \"object.h\"\n> +\n> +int merge_three_way(struct repository *r,\n> +\t\t    const struct object_id *orig_blob,\n> +\t\t    const struct object_id *our_blob,\n> +\t\t    const struct object_id *their_blob, const char *path,\n> +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n> +\n> +#endif /* MERGE_STRATEGIES_H */\n> diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\n> index 2eddcc7664..5fb74e39a0 100755\n> --- a/t/t6415-merge-dir-to-symlink.sh\n> +++ b/t/t6415-merge-dir-to-symlink.sh\n> @@ -94,7 +94,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n>  \ttest -h a/b\n>  '\n>  \n> -test_expect_failure 'do not lose untracked in merge (resolve)' '\n> +test_expect_success 'do not lose untracked in merge (resolve)' '\n>  \tgit reset --hard &&\n>  \tgit checkout baseline^0 &&\n>  \t>a/b/c/e &&\n"},{"id":"413351","messageId":"30de2a29-a3fe-57f7-76b4-5d2bd3688635@gmail.com","threadId":"53755","inReplyTo":"xmqq5z4th6ak.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6 04/13] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-01-03T22:41:06Z","receivedAt":"2021-01-03T22:42:20Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nThank you for your comments.\n\nLe 22/12/2020 à 22:36, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> +int merge_three_way(struct repository *r,\n>> +\t\t    const struct object_id *orig_blob,\n>> +\t\t    const struct object_id *our_blob,\n>> +\t\t    const struct object_id *their_blob, const char *path,\n>> +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n>> +{\n>> +\tif (orig_blob &&\n>> +\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n>> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n>> +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n>> +\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n>> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n> \n> When both ours and theirs deleted, by definition orig_blob cannot be\n> NULL, so \"orig_blob &&\" part would be true, but the other side that\n> requires either (!their && our) or (!our && their) is true cannot be\n> satisfied.  So it seems that the comment does not match the behaviour.\n> \n> You'd need \"(!their_blob && !our_blob) ||\" in the second part?\n> \n\nYes, you're correct.\n\n> This shows lack of test coverage, I think A manual test seems to\n> trigger the \"unhandled case\" error you added:\n> \n> $ make\n> $ ./git-merge-one-file $(git rev-parse :COPYING) \"\" \"\" \\\n> \tCOPYING \\\n> \t100644 \"\" \"\"\n> error: COPYING: Not handling case 536e55524db72bd2acf175208aef4f3dfc148d42 ->  ->\n> \n\nOkay, I will add a test case for this.\n\n>> +\t} else if (!orig_blob && our_blob && !their_blob) {\n>> +\t\t/*\n>> +\t\t * Added in one.  The other side did not add and we\n>> +\t\t * added so there is nothing to be done, except making\n>> +\t\t * the path merged.\n>> +\t\t */\n> \n> This is not the sole \"Added in one\" case.  The next elseif arm also\n> is added in one.\n> \n> What is notable in this elseif arm is that this is \"added in ours\",\n> which allows us (and forces us) not to touch the working tree with\n> extra \"checkout\".  So either remove \"Added in one\" from here for\n> symmetry with the next elseif arm, or better yet say \"Added in\n> ours\".\n> \n>> +\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob,\n>> +\t\t\t\t\t      path, 0, 1, 1, NULL);\n> \n> All callers to add_to_index_cacheinfo() uses 0, 1, 1 for stage,\n> allow_add and allow_replace, except for the original.  The new\n> callers you added should not have to keep repeating 0, 1, 1 like\n> this caller does (see below).\n> \n>> +\t} else if (!orig_blob && !our_blob && their_blob) {\n>> +\t\tstruct cache_entry *ce;\n>> +\t\tprintf(_(\"Adding %s\\n\"), path);\n>> +\n>> +\t\tif (file_exists(path))\n>> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n>> +\n>> +\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob,\n>> +\t\t\t\t\t   path, 0, 1, 1, &ce))\n>> +\t\t\treturn -1;\n>> +\t\treturn checkout_from_index(r->index, path, ce);\n> \n> \"git grep -A4 -e add_to_index_cacheinfo\" after applying all patches\n> in the series shows us that the &ce parameter was added only to call\n> checkout_from_index() using it.\n> \n> I doubt add_to_index_cacheinfo() is the right interface for this\n> series.  This caller (and all other callers in the series that calls\n> add_to_index_cacheinfo(), followed by checkout_from_index()) rather\n> wants to have a function (defined in <cache.h>):\n> \n> \textern int add_merge_result_to_index(struct index_state, *\n> \t\t\tunsigned int mode,\n>                         const struct object_id *oid,\n> \t\t\tconst char *path,\n> \t\t\tint checkout);\n> \n> with which the last 4 lines of the above hunk can just become\n> \n> \t\treturn add_merge_result_to_index(r->index,\n> \t\t\ttheir_mode, their_blob, path, 1);\n> \n> I would think.  The earlier caller to add_to_index_cacheinfo() for\n> \"ours is the result\" can pass 0 to the checkout parameter so the\n> helper won't make a call to checkout_from_index().\n> \n> And the step to add that helper would be in this patch (it could be\n> after the previous step and before this step, but it is probably\n> easier to understand if the new helper is introduced with its\n> callers).\n> \n> If we were to do that, then I do not mind the repetition of 0, 1, 1\n> too much.\n> \n\nOkay.  Are we sure we want add_merge_result_to_index() inside\nread-cache.c/cache.h?\n\nCheers,\nAlban\n\n"},{"id":"413463","messageId":"2ff7cebf-0084-aef8-bf82-d76a82be23e7@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-6-alban.gruin@gmail.com","subject":"Re: [PATCH v6 05/13] merge-index: libify merge_one_path() and merge_all()","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-01-05T15:59:07Z","receivedAt":"2021-01-05T15:59:51Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n> merge-one-file', they delegate the work to another git command, `git\n> merge-index', that will loop over files in the index and call the\n> specified command.  Unfortunately, these functions are not part of\n> libgit.a, which means that once rewritten, the strategies would still\n> have to invoke `merge-one-file' by spawning a new process first.\n\nThis is a good thing to do.\n \n> To avoid this, this moves and renames merge_one_path(), merge_all(), and\n> their helpers to merge-strategies.c.  They also take a callback to\n> dictate what they should do for each file.  For now, to preserve the\n> behaviour of `merge-index', only one callback, launching a new process,\n> is defined.\n\nI don't think the callback should be in libgit.a, though. The callback\nitself should be a static method inside builtin/merge-index.c.\n\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  builtin/merge-index.c |  77 +++----------------------------\n>  merge-strategies.c    | 104 ++++++++++++++++++++++++++++++++++++++++++\n>  merge-strategies.h    |  19 ++++++++\n>  3 files changed, 130 insertions(+), 70 deletions(-)\n> \n> diff --git a/builtin/merge-index.c b/builtin/merge-index.c\n> index 38ea6ad6ca..d5e5713b25 100644\n> --- a/builtin/merge-index.c\n> +++ b/builtin/merge-index.c\n> @@ -1,74 +1,11 @@\n>  #define USE_THE_INDEX_COMPATIBILITY_MACROS\n>  #include \"builtin.h\"\n> -#include \"run-command.h\"\n> -\n> -static const char *pgm;\n> -static int one_shot, quiet;\n> -static int err;\n> -\n> -static int merge_entry(int pos, const char *path)\n> -{\n> -\tint found;\n> -\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n> -\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n> -\tchar ownbuf[4][60];\n> -\n> -\tif (pos >= active_nr)\n> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n> -\tfound = 0;\n> -\tdo {\n> -\t\tconst struct cache_entry *ce = active_cache[pos];\n> -\t\tint stage = ce_stage(ce);\n> -\n> -\t\tif (strcmp(ce->name, path))\n> -\t\t\tbreak;\n> -\t\tfound++;\n> -\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n> -\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n> -\t\targuments[stage] = hexbuf[stage];\n> -\t\targuments[stage + 4] = ownbuf[stage];\n> -\t} while (++pos < active_nr);\n> -\tif (!found)\n> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n> -\n> -\tif (run_command_v_opt(arguments, 0)) {\n> -\t\tif (one_shot)\n> -\t\t\terr++;\n> -\t\telse {\n> -\t\t\tif (!quiet)\n> -\t\t\t\tdie(\"merge program failed\");\n> -\t\t\texit(1);\n> -\t\t}\n> -\t}\n> -\treturn found;\n> -}\n> -\n> -static void merge_one_path(const char *path)\n> -{\n> -\tint pos = cache_name_pos(path, strlen(path));\n> -\n> -\t/*\n> -\t * If it already exists in the cache as stage0, it's\n> -\t * already merged and there is nothing to do.\n> -\t */\n> -\tif (pos < 0)\n> -\t\tmerge_entry(-pos-1, path);\n> -}\n> -\n> -static void merge_all(void)\n> -{\n> -\tint i;\n> -\tfor (i = 0; i < active_nr; i++) {\n> -\t\tconst struct cache_entry *ce = active_cache[i];\n> -\t\tif (!ce_stage(ce))\n> -\t\t\tcontinue;\n> -\t\ti += merge_entry(i, ce->name)-1;\n> -\t}\n> -}\n> +#include \"merge-strategies.h\"\n>  \n>  int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  {\n> -\tint i, force_file = 0;\n> +\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n> +\tconst char *pgm;\n>  \n>  \t/* Without this we cannot rely on waitpid() to tell\n>  \t * what happened to our children.\n> @@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  \t\t\t\tcontinue;\n>  \t\t\t}\n>  \t\t\tif (!strcmp(arg, \"-a\")) {\n> -\t\t\t\tmerge_all();\n> +\t\t\t\terr |= merge_all_index(the_repository, one_shot, quiet,\n> +\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n\nThis hunk makes it look like pgm is uninitialized, but it is set earlier\nin cmd_merge_index() (previously referring to the global instance). Good.\n\n> +int merge_one_file_spawn(struct repository *r,\n> +\t\t\t const struct object_id *orig_blob,\n> +\t\t\t const struct object_id *our_blob,\n> +\t\t\t const struct object_id *their_blob, const char *path,\n> +\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\t void *data)\n> +{\n> +\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n> +\tchar modes[3][10] = {{0}};\n> +\tconst char *arguments[] = { (char *)data, oids[0], oids[1], oids[2],\n> +\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n> +\n> +\tif (orig_blob) {\n> +\t\toid_to_hex_r(oids[0], orig_blob);\n> +\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n> +\t}\n> +\n> +\tif (our_blob) {\n> +\t\toid_to_hex_r(oids[1], our_blob);\n> +\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n> +\t}\n> +\n> +\tif (their_blob) {\n> +\t\toid_to_hex_r(oids[2], their_blob);\n> +\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n> +\t}\n> +\n> +\treturn run_command_v_opt(arguments, 0);\n> +}\n\nYes, this would be better in the builtin code. Better to keep the meaning\nof 'data' clear in the context of that file.\n\n> +static int merge_entry(struct repository *r, int quiet, unsigned int pos,\n> +\t\t       const char *path, int *err, merge_fn fn, void *data)\n> +{\n> +\tint found = 0;\n> +\tconst struct object_id *oids[3] = {NULL};\n> +\tunsigned int modes[3] = {0};\n> +\n> +\tdo {\n> +\t\tconst struct cache_entry *ce = r->index->cache[pos];\n> +\t\tint stage = ce_stage(ce);\n> +\n> +\t\tif (strcmp(ce->name, path))\n> +\t\t\tbreak;\n> +\t\tfound++;\n> +\t\toids[stage - 1] = &ce->oid;\n> +\t\tmodes[stage - 1] = ce->ce_mode;\n> +\t} while (++pos < r->index->cache_nr);\n> +\tif (!found)\n> +\t\treturn error(_(\"%s is not in the cache\"), path);\n> +\n> +\tif (fn(r, oids[0], oids[1], oids[2], path,\n> +\t       modes[0], modes[1], modes[2], data)) {\n> +\t\tif (!quiet)\n> +\t\t\terror(_(\"Merge program failed\"));\n> +\t\t(*err)++;\n> +\t}\n> +\n> +\treturn found;\n> +}\n> +\n> +int merge_index_path(struct repository *r, int oneshot, int quiet,\n> +\t\t     const char *path, merge_fn fn, void *data)\n> +{\n> +\tint pos = index_name_pos(r->index, path, strlen(path)), ret, err = 0;\n> +\n> +\t/*\n> +\t * If it already exists in the cache as stage0, it's\n> +\t * already merged and there is nothing to do.\n> +\t */\n> +\tif (pos < 0) {\n> +\t\tret = merge_entry(r, quiet || oneshot, -pos - 1, path, &err, fn, data);\n> +\t\tif (ret == -1)\n> +\t\t\treturn -1;\n> +\t\telse if (err)\n> +\t\t\treturn 1;\n> +\t}\n> +\treturn 0;\n> +}\n> +\n> +int merge_all_index(struct repository *r, int oneshot, int quiet,\n> +\t\t    merge_fn fn, void *data)\n> +{\n> +\tint err = 0, ret;\n> +\tunsigned int i;\n> +\n> +\tfor (i = 0; i < r->index->cache_nr; i++) {\n> +\t\tconst struct cache_entry *ce = r->index->cache[i];\n> +\t\tif (!ce_stage(ce))\n> +\t\t\tcontinue;\n> +\n> +\t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n> +\t\tif (ret > 0)\n> +\t\t\ti += ret - 1;\n> +\t\telse if (ret == -1)\n> +\t\t\treturn -1;\n> +\n> +\t\tif (err && !oneshot)\n> +\t\t\treturn 1;\n> +\t}\n> +\n> +\treturn err;\n> +}\n\nI notice that these methods don't actually use the repository pointer\nmore than they just use 'r->index'. Should they instead take a\n'struct index_state *istate' directly? (I see that the repository is\nused later by merge_strategies_resolve(), but not in these.)\n\nIf you think it likely that we will need a repository for these methods,\nthen feel free to ignore me and keep your 'r' pointer.\n\nThanks,\n-Stolee\n"},{"id":"413464","messageId":"44c9189d-9d2f-c437-d0d6-9529708d2c99@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-7-alban.gruin@gmail.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-01-05T16:11:19Z","receivedAt":"2021-01-05T16:12:22Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> +\n>  \tpgm = argv[i++];\n> +\tsetup_work_tree();\n> +\n> +\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n\nThis stood out to me as possibly fragile. What if we call the\nnon-dashed form \"git merge-one-file\"? Shouldn't we be doing so?\n\nOr, is this something that is handled higher in the builtin\nmachinery to take the non-dashed version and change it to the\ndashed version for historical reasons?\n\n> +\t\tmerge_action = merge_one_file_func;\n> +\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n> +\t} else {\n> +\t\tmerge_action = merge_one_file_spawn;\n> +\t\tdata = (void *)pgm;\n> +\t}\n> +\n\n...\n\n> +\tif (merge_action == merge_one_file_func) {\n\nnit: This made me think it would be better to check the 'lock'\nitself to see if it was initialized or not. Perhaps\n\n\tif (lock.tempfile) {\n\nwould be the appropriate way to check this?\n\nFor now, this is equivalent behavior, but it might be helpful if\nwe add more cases that take the lock in the future.\n\n> +\t\tif (err) {\n> +\t\t\trollback_lock_file(&lock);\n> +\t\t\treturn err;\n> +\t\t}\n> +\n> +\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>  \t}\n>  \treturn err;\n\nnit: this could be simplified. In total, I recommend:\n\n\tif (lock.tempfile) {\n\t\tif (err)\n\t\t\trollback_lock_file(&lock);\n\t\telse\n\t\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n\t}\n\treturn err;\n\n\n>  }\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index 6f27e66dfe..542cefcf3d 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -178,6 +178,18 @@ int merge_three_way(struct repository *r,\n>  \treturn 0;\n>  }\n>  \n> +int merge_one_file_func(struct repository *r,\n> +\t\t\tconst struct object_id *orig_blob,\n> +\t\t\tconst struct object_id *our_blob,\n> +\t\t\tconst struct object_id *their_blob, const char *path,\n> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\tvoid *data)\n> +{\n> +\treturn merge_three_way(r,\n> +\t\t\t       orig_blob, our_blob, their_blob, path,\n> +\t\t\t       orig_mode, our_mode, their_mode);\n> +}\n> +\n\nAgain, I don't recommend making this callback in the library. Instead, keep\nit in the builtin and then use merge_three_way() which is in the library.\n\n>  int merge_one_file_spawn(struct repository *r,\n>  \t\t\t const struct object_id *orig_blob,\n>  \t\t\t const struct object_id *our_blob,\n> @@ -261,17 +273,22 @@ int merge_all_index(struct repository *r, int oneshot, int quiet,\n>  \t\t    merge_fn fn, void *data)\n>  {\n>  \tint err = 0, ret;\n> -\tunsigned int i;\n> +\tunsigned int i, prev_nr;\n>  \n>  \tfor (i = 0; i < r->index->cache_nr; i++) {\n>  \t\tconst struct cache_entry *ce = r->index->cache[i];\n>  \t\tif (!ce_stage(ce))\n>  \t\t\tcontinue;\n>  \n> +\t\tprev_nr = r->index->cache_nr;\n>  \t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n> -\t\tif (ret > 0)\n> -\t\t\ti += ret - 1;\n> -\t\telse if (ret == -1)\n> +\t\tif (ret > 0) {\n> +\t\t\t/* Don't bother handling an index that has\n> +\t\t\t   grown, since merge_one_file_func() can't grow\n> +\t\t\t   it, and merge_one_file_spawn() can't change\n> +\t\t\t   it. */\n\nmulti-line comment style is as follows:\n\n\t/*\n\t * Don't bother handling an index that has\n\t * grown, since merge_one_file_func() can't grow\n\t * it, and merge_one_file_spawn() can't change it.\n\t */\n\nThanks,\n-Stolee\n"},{"id":"413465","messageId":"4efb8024-b8c4-a69e-c5ce-4ad43781ecc3@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-9-alban.gruin@gmail.com","subject":"Re: [PATCH v6 08/13] merge-recursive: move better_branch_name() to merge.c","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-01-05T16:19:36Z","receivedAt":"2021-01-05T16:20:20Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> better_branch_name() will be used by merge-octopus once it is rewritten\n> in C, so instead of duplicating it, this moves this function\n> preventively inside an appropriate file in libgit.a.  This function is\n> also renamed to reflect its usage by merge strategies.\n\ns/preventively/preemptively/\n\n> diff --git a/cache.h b/cache.h\n> index be16ab3215..2d844576ea 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -1933,7 +1933,7 @@ int checkout_fast_forward(struct repository *r,\n>  \t\t\t  const struct object_id *from,\n>  \t\t\t  const struct object_id *to,\n>  \t\t\t  int overwrite_ignore);\n> -\n> +char *merge_get_better_branch_name(const char *branch);\n>  \n>  int sane_execvp(const char *file, char *const argv[]);\n\nI tend to avoid adding new things to the enormous cache.h, but the\nbest place I could see was refs.h next to repo_default_branch_name().\n\nMaybe cache.h is fine.\n\n-Stolee\n\n"},{"id":"413468","messageId":"53b6ca2a-771a-dd6a-ce17-b2cb8d60ec65@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-10-alban.gruin@gmail.com","subject":"Re: [PATCH v6 09/13] merge-octopus: rewrite in C","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-01-05T16:40:52Z","receivedAt":"2021-01-05T16:41:40Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> This rewrites `git merge-octopus' from shell to C.  As for the two last\n> conversions, this port removes calls to external processes to avoid\n> reading and writing the index over and over again.\n\n...\n\n> diff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\n> new file mode 100644\n> index 0000000000..ca8f9f345d\n> --- /dev/null\n> +++ b/builtin/merge-octopus.c\n> @@ -0,0 +1,69 @@\n> +/*\n> + * Builtin \"git merge-octopus\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-octopus.sh, written by Junio C Hamano.\n> + *\n> + * Resolve two or more trees.\n> + */\n> +\n> +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n\nHm. It would be best if this was not added to any new code. Please\nsquash in changes that avoid these macros.\n\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"commit.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_octopus_usage[] =\n> +\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n> +\n> +int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint i, sep_seen = 0;\n\nOne strategy I've been trying to do, when I remember, is to start each\nbuiltin with\n\n\tstruct repository *repo = the_repository;\n\nthen use 'repo' over 'the_repository' and other macros. This gets us\ncloser to the state where the builtin cmd_*() methods could take a\nrepository pointer as a parameter. (We are a ways off, but every little\nbit helps, right?)\n\n> +\t/*\n> +\t * Reject if this is not an octopus -- resolve should be used\n> +\t * instead.\n> +\t */\n> +\tif (commit_list_count(remotes) < 2)\n> +\t\treturn 2;\n\nThis caused me to pause, since it might be nice to have a warning message\nhere. However, it is identical behavior to the script, including the\ncomment.\n\nIt appears that there is no Documentation/git-merge-octopus.txt, but such\na doc file would want to include the meaning of exit code 2.\n\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index 9aa07e91b5..4d9dd55296 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -1,5 +1,6 @@\n>  #include \"cache.h\"\n>  #include \"cache-tree.h\"\n> +#include \"commit-reach.h\"\n\nYou had my curiosity, but now you have my attention. ;)\n\n> +int merge_strategies_octopus(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remotes)\n> +{\n> +\tint ff_merge = 1, ret = 0, references = 1;\n> +\tstruct commit **reference_commit, *head_commit;\n\n'reference_commit' might be clearer if it was plural, right?\n\n> +\tstruct tree *reference_tree, *head_tree;\n> +\tstruct commit_list *i;\n> +\tstruct object_id head;\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\n> +\tget_oid(head_arg, &head);\n> +\thead_commit = lookup_commit_reference(r, &head);\n> +\thead_tree = repo_get_commit_tree(r, head_commit);\n> +\n> +\tif (parse_tree(head_tree))\n> +\t\treturn 2;\n> +\n> +\tif (repo_index_has_changes(r, head_tree, &sb)) {\n> +\t\terror(_(\"Your local changes to the following files \"\n> +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n> +\t\t      sb.buf);\n> +\t\tstrbuf_release(&sb);\n> +\t\treturn 2;\n> +\t}\n> +\n> +\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n> +\t\t\t\t   sizeof(struct commit *));\n> +\treference_commit[0] = head_commit;\n> +\treference_tree = head_tree;\n> +\n> +\tfor (i = remotes; i && i->item; i = i->next) {\n> +\t\tstruct commit *c = i->item;\n> +\t\tstruct object_id *oid = &c->object.oid;\n> +\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n> +\t\tstruct commit_list *common, *j;\n> +\t\tchar *branch_name;\n> +\t\tint k = 0, up_to_date = 0;\n> +\n> +\t\tif (ret) {\n> +\t\t\t/*\n> +\t\t\t * We allow only last one to have a\n> +\t\t\t * hand-resolvable conflicts.  Last round failed\n> +\t\t\t * and we still had a head to merge.\n> +\t\t\t */\n> +\t\t\tputs(_(\"Automated merge did not work.\"));\n> +\t\t\tputs(_(\"Should not be doing an octopus.\"));\n> +\n> +\t\t\tfree(reference_commit);\n> +\t\t\treturn 2;\n> +\t\t}\n> +\n> +\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n> +\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n\nHere we are. You should probably use repo_get_merge_bases_many().\n\n'references' is not a list, but instead a count. Could\nit be renamed nr_references or something?\n\n> +\n> +\t\tif (!common) {\n> +\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n> +\n> +\t\t\tfree(branch_name);\n> +\t\t\tfree_commit_list(common);\n> +\t\t\tfree(reference_commit);\n> +\n> +\t\t\treturn 2;\n\nhm. we are getting into magic constant territory. Perhaps this should\nbe marked with a macro in merge-strategies.h? It could be used in the\ncase of \"only two heads\" as well.\n\n> +\t\t}\n> +\n> +\t\tfor (j = common; j && !(up_to_date || !ff_merge); j = j->next) {\n> +\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n> +\n> +\t\t\tif (k < references)\n> +\t\t\t\tff_merge &= oideq(&j->item->object.oid, &reference_commit[k++]->object.oid);\n\nI'm confused about this line. Shouldn't we care only about\nreference_commit[references]? If we _do_ care about all possible\nreference_commit[k] values, then shouldn't this be a loop over the\nk values, not a single check per k (and advancing as we iterate\nthrough the results from common)?\n\nSeems we could use some test cases around criss-cross octopus\nmerges (i.e. multiple merge bases).\n\n> +\t\t}\n> +\n> +\t\tif (up_to_date) {\n> +\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n> +\n> +\t\t\tfree(branch_name);\n> +\t\t\tfree_commit_list(common);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tif (ff_merge) {\n> +\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n> +\t\t\t\t\t\t   current_tree, &reference_tree);\n> +\t\t\treferences = 0;\n> +\t\t} else {\n> +\t\t\tret = octopus_do_merge(r, branch_name, common,\n> +\t\t\t\t\t       current_tree, &reference_tree);\n> +\t\t}\n> +\n> +\t\tfree(branch_name);\n> +\t\tfree_commit_list(common);\n> +\n> +\t\tif (ret == -1)\n> +\t\t\tbreak;\n> +\n> +\t\treference_commit[references++] = c;\n> +\t}\n> +\n> +\tfree(reference_commit);\n> +\treturn ret;\n> +}\n\nThis patch could use a little work, but it's a good start.\n\nThanks,\n-Stolee\n\n"},{"id":"413469","messageId":"8a0ce635-261d-3099-dc10-eabd7d41332f@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-11-alban.gruin@gmail.com","subject":"Re: [PATCH v6 10/13] merge: use the \"resolve\" strategy without forking","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-01-05T16:45:21Z","receivedAt":"2021-01-05T16:46:09Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> This teaches `git merge' to invoke the \"resolve\" strategy with a\n> function call instead of forking.\n...\n> @@ -740,6 +741,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n>  \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n>  \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n>  \t\treturn clean ? 0 : 1;\n> +\t} else if (!strcmp(strategy, \"resolve\")) {\n> +\t\treturn merge_strategies_resolve(the_repository, common,\n> +\t\t\t\t\t\thead_arg, remoteheads);\n>  \t} else {\n>  \t\treturn try_merge_command(the_repository,\n>  \t\t\t\t\t strategy, xopts_nr, xopts,\n> \n\nThis is a very satisfying change.\n\nThanks,\n-Stolee\n"},{"id":"413470","messageId":"25a92f4c-1d7e-45bb-0a05-2f82586867ad@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"Re: [PATCH v6 00/13] Rewrite the remaining merge strategies from shell to C","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2021-01-05T16:50:04Z","receivedAt":"2021-01-05T16:50:56Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> In a effort to reduce the number of shell scripts in git's codebase, I\n> propose this patch series converting the two remaining merge strategies,\n> resolve and octopus, from shell to C.  This will enable slightly better\n> performance, better integration with git itself (no more forking to\n> perform these operations), better portability (Windows and shell scripts\n> don't mix well).\n> \n> Three scripts are actually converted: first git-merge-one-file.sh, then\n> git-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\n> are converted, but they also are modified to operate without forking,\n> and then libified so they can be used by git without spawning another\n> process.\n\nThis is a worthwhile effort. Of course, I wasn't familiar with this\narea and only took interest when I started working in a conflicting\narea.\n\nI did my best in reviewing the content here. I did not comment further\non the patches where Junio already gave extensive review.\n\n> This series keeps the commands `git merge-one-file', `git\n> merge-resolve', and `git merge-octopus', so any script depending on them\n> should keep working without any changes.\n\nI pointed out some questions about the \"dashed versus non-dashed\"\nforms.\n\n> This series is based on 306ee63a70 (Eighteenth batch, 2020-09-29).  The\n> tip is tagged as \"rewrite-merge-strategies-v6\" at\n> https://github.com/agrn/git.\n\nPlease also base onto 722fc37491 (help: do not expect built-in\ncommands to be hardlinked, 2020-10-07) as requested by Szeder.\n\nThanks,\n-Stolee\n"},{"id":"413472","messageId":"CAN0heSrOKr--GenbowHP+iwkijbg5pCeJLq+wz6NXCXTsfcvGg@mail.gmail.com","threadId":"53755","inReplyTo":"44c9189d-9d2f-c437-d0d6-9529708d2c99@gmail.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2021-01-05T17:35:33Z","receivedAt":"2021-01-05T17:36:37Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On Tue, 5 Jan 2021 at 17:13, Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 11/24/2020 6:53 AM, Alban Gruin wrote:\n> > +     if (merge_action == merge_one_file_func) {\n>\n> nit: This made me think it would be better to check the 'lock'\n> itself to see if it was initialized or not. Perhaps\n>\n>         if (lock.tempfile) {\n>\n> would be the appropriate way to check this?\n\n> nit: this could be simplified. In total, I recommend:\n>\n>         if (lock.tempfile) {\n>                 if (err)\n>                         rollback_lock_file(&lock);\n>                 else\n>                         return write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>         }\n>         return err;\n\nFWIW, I also find that way of writing it easier to grok. Although,\nrather than peeking at `lock.tempfile`, I suggest using\n`is_lock_file_locked(&lock)`.\n\nMartin\n"},{"id":"413513","messageId":"83da1bc1-d178-ee19-cb34-5bf023477905@gmail.com","threadId":"53755","inReplyTo":"2ff7cebf-0084-aef8-bf82-d76a82be23e7@gmail.com","subject":"Re: [PATCH v6 05/13] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-01-05T23:20:06Z","receivedAt":"2021-01-05T23:21:17Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Derrick,\n\nLe 05/01/2021 à 16:59, Derrick Stolee a écrit :\n> On 11/24/2020 6:53 AM, Alban Gruin wrote:\n>> The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n>> merge-one-file', they delegate the work to another git command, `git\n>> merge-index', that will loop over files in the index and call the\n>> specified command.  Unfortunately, these functions are not part of\n>> libgit.a, which means that once rewritten, the strategies would still\n>> have to invoke `merge-one-file' by spawning a new process first.\n> \n> This is a good thing to do.\n>  \n>> To avoid this, this moves and renames merge_one_path(), merge_all(), and\n>> their helpers to merge-strategies.c.  They also take a callback to\n>> dictate what they should do for each file.  For now, to preserve the\n>> behaviour of `merge-index', only one callback, launching a new process,\n>> is defined.\n> \n> I don't think the callback should be in libgit.a, though. The callback\n> itself should be a static method inside builtin/merge-index.c.\n> \n\nRight.  Modern code should not use this callback -- or the merge-index\nbuiltin once this gets merged.\n\n>> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n>> ---\n>>  builtin/merge-index.c |  77 +++----------------------------\n>>  merge-strategies.c    | 104 ++++++++++++++++++++++++++++++++++++++++++\n>>  merge-strategies.h    |  19 ++++++++\n>>  3 files changed, 130 insertions(+), 70 deletions(-)\n>>\n>> diff --git a/builtin/merge-index.c b/builtin/merge-index.c\n>> index 38ea6ad6ca..d5e5713b25 100644\n>> --- a/builtin/merge-index.c\n>> +++ b/builtin/merge-index.c\n>> @@ -1,74 +1,11 @@\n>>  #define USE_THE_INDEX_COMPATIBILITY_MACROS\n>>  #include \"builtin.h\"\n>> -#include \"run-command.h\"\n>> -\n>> -static const char *pgm;\n>> -static int one_shot, quiet;\n>> -static int err;\n>> -\n>> -static int merge_entry(int pos, const char *path)\n>> -{\n>> -\tint found;\n>> -\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n>> -\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n>> -\tchar ownbuf[4][60];\n>> -\n>> -\tif (pos >= active_nr)\n>> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n>> -\tfound = 0;\n>> -\tdo {\n>> -\t\tconst struct cache_entry *ce = active_cache[pos];\n>> -\t\tint stage = ce_stage(ce);\n>> -\n>> -\t\tif (strcmp(ce->name, path))\n>> -\t\t\tbreak;\n>> -\t\tfound++;\n>> -\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n>> -\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n>> -\t\targuments[stage] = hexbuf[stage];\n>> -\t\targuments[stage + 4] = ownbuf[stage];\n>> -\t} while (++pos < active_nr);\n>> -\tif (!found)\n>> -\t\tdie(\"git merge-index: %s not in the cache\", path);\n>> -\n>> -\tif (run_command_v_opt(arguments, 0)) {\n>> -\t\tif (one_shot)\n>> -\t\t\terr++;\n>> -\t\telse {\n>> -\t\t\tif (!quiet)\n>> -\t\t\t\tdie(\"merge program failed\");\n>> -\t\t\texit(1);\n>> -\t\t}\n>> -\t}\n>> -\treturn found;\n>> -}\n>> -\n>> -static void merge_one_path(const char *path)\n>> -{\n>> -\tint pos = cache_name_pos(path, strlen(path));\n>> -\n>> -\t/*\n>> -\t * If it already exists in the cache as stage0, it's\n>> -\t * already merged and there is nothing to do.\n>> -\t */\n>> -\tif (pos < 0)\n>> -\t\tmerge_entry(-pos-1, path);\n>> -}\n>> -\n>> -static void merge_all(void)\n>> -{\n>> -\tint i;\n>> -\tfor (i = 0; i < active_nr; i++) {\n>> -\t\tconst struct cache_entry *ce = active_cache[i];\n>> -\t\tif (!ce_stage(ce))\n>> -\t\t\tcontinue;\n>> -\t\ti += merge_entry(i, ce->name)-1;\n>> -\t}\n>> -}\n>> +#include \"merge-strategies.h\"\n>>  \n>>  int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>>  {\n>> -\tint i, force_file = 0;\n>> +\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n>> +\tconst char *pgm;\n>>  \n>>  \t/* Without this we cannot rely on waitpid() to tell\n>>  \t * what happened to our children.\n>> @@ -98,14 +35,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>>  \t\t\t\tcontinue;\n>>  \t\t\t}\n>>  \t\t\tif (!strcmp(arg, \"-a\")) {\n>> -\t\t\t\tmerge_all();\n>> +\t\t\t\terr |= merge_all_index(the_repository, one_shot, quiet,\n>> +\t\t\t\t\t\t       merge_one_file_spawn, (void *)pgm);\n> \n> This hunk makes it look like pgm is uninitialized, but it is set earlier\n> in cmd_merge_index() (previously referring to the global instance). Good.\n> \n>> +int merge_one_file_spawn(struct repository *r,\n>> +\t\t\t const struct object_id *orig_blob,\n>> +\t\t\t const struct object_id *our_blob,\n>> +\t\t\t const struct object_id *their_blob, const char *path,\n>> +\t\t\t unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t\t void *data)\n>> +{\n>> +\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n>> +\tchar modes[3][10] = {{0}};\n>> +\tconst char *arguments[] = { (char *)data, oids[0], oids[1], oids[2],\n>> +\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n>> +\n>> +\tif (orig_blob) {\n>> +\t\toid_to_hex_r(oids[0], orig_blob);\n>> +\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n>> +\t}\n>> +\n>> +\tif (our_blob) {\n>> +\t\toid_to_hex_r(oids[1], our_blob);\n>> +\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n>> +\t}\n>> +\n>> +\tif (their_blob) {\n>> +\t\toid_to_hex_r(oids[2], their_blob);\n>> +\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n>> +\t}\n>> +\n>> +\treturn run_command_v_opt(arguments, 0);\n>> +}\n> \n> Yes, this would be better in the builtin code. Better to keep the meaning\n> of 'data' clear in the context of that file.\n> \n>> +static int merge_entry(struct repository *r, int quiet, unsigned int pos,\n>> +\t\t       const char *path, int *err, merge_fn fn, void *data)\n>> +{\n>> +\tint found = 0;\n>> +\tconst struct object_id *oids[3] = {NULL};\n>> +\tunsigned int modes[3] = {0};\n>> +\n>> +\tdo {\n>> +\t\tconst struct cache_entry *ce = r->index->cache[pos];\n>> +\t\tint stage = ce_stage(ce);\n>> +\n>> +\t\tif (strcmp(ce->name, path))\n>> +\t\t\tbreak;\n>> +\t\tfound++;\n>> +\t\toids[stage - 1] = &ce->oid;\n>> +\t\tmodes[stage - 1] = ce->ce_mode;\n>> +\t} while (++pos < r->index->cache_nr);\n>> +\tif (!found)\n>> +\t\treturn error(_(\"%s is not in the cache\"), path);\n>> +\n>> +\tif (fn(r, oids[0], oids[1], oids[2], path,\n>> +\t       modes[0], modes[1], modes[2], data)) {\n>> +\t\tif (!quiet)\n>> +\t\t\terror(_(\"Merge program failed\"));\n>> +\t\t(*err)++;\n>> +\t}\n>> +\n>> +\treturn found;\n>> +}\n>> +\n>> +int merge_index_path(struct repository *r, int oneshot, int quiet,\n>> +\t\t     const char *path, merge_fn fn, void *data)\n>> +{\n>> +\tint pos = index_name_pos(r->index, path, strlen(path)), ret, err = 0;\n>> +\n>> +\t/*\n>> +\t * If it already exists in the cache as stage0, it's\n>> +\t * already merged and there is nothing to do.\n>> +\t */\n>> +\tif (pos < 0) {\n>> +\t\tret = merge_entry(r, quiet || oneshot, -pos - 1, path, &err, fn, data);\n>> +\t\tif (ret == -1)\n>> +\t\t\treturn -1;\n>> +\t\telse if (err)\n>> +\t\t\treturn 1;\n>> +\t}\n>> +\treturn 0;\n>> +}\n>> +\n>> +int merge_all_index(struct repository *r, int oneshot, int quiet,\n>> +\t\t    merge_fn fn, void *data)\n>> +{\n>> +\tint err = 0, ret;\n>> +\tunsigned int i;\n>> +\n>> +\tfor (i = 0; i < r->index->cache_nr; i++) {\n>> +\t\tconst struct cache_entry *ce = r->index->cache[i];\n>> +\t\tif (!ce_stage(ce))\n>> +\t\t\tcontinue;\n>> +\n>> +\t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n>> +\t\tif (ret > 0)\n>> +\t\t\ti += ret - 1;\n>> +\t\telse if (ret == -1)\n>> +\t\t\treturn -1;\n>> +\n>> +\t\tif (err && !oneshot)\n>> +\t\t\treturn 1;\n>> +\t}\n>> +\n>> +\treturn err;\n>> +}\n> \n> I notice that these methods don't actually use the repository pointer\n> more than they just use 'r->index'. Should they instead take a\n> 'struct index_state *istate' directly? (I see that the repository is\n> used later by merge_strategies_resolve(), but not in these.)\n> \n> If you think it likely that we will need a repository for these methods,\n> then feel free to ignore me and keep your 'r' pointer.\n> \n\nOuch, you're right.  I thought this was necessary because\nmerge_three_way() wanted a `struct repository *', without noticing that\nit was in fact unnecessary, even in my follow-up patch.  I change that.\n\n> Thanks,\n> -Stolee\n> \n\nCheers,\nAlban\n\n"},{"id":"413514","messageId":"a0c44e65-bad5-5fb1-9aea-806260a6294e@gmail.com","threadId":"53755","inReplyTo":"CAN0heSrOKr--GenbowHP+iwkijbg5pCeJLq+wz6NXCXTsfcvGg@mail.gmail.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-01-05T23:20:36Z","receivedAt":"2021-01-05T23:21:57Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Martin & Derrick,\n\nLe 05/01/2021 à 18:35, Martin Ågren a écrit :\n> On Tue, 5 Jan 2021 at 17:13, Derrick Stolee <stolee@gmail.com> wrote:\n>>\n>> On 11/24/2020 6:53 AM, Alban Gruin wrote:\n>>> +     if (merge_action == merge_one_file_func) {\n>>\n>> nit: This made me think it would be better to check the 'lock'\n>> itself to see if it was initialized or not. Perhaps\n>>\n>>         if (lock.tempfile) {\n>>\n>> would be the appropriate way to check this?\n> \n>> nit: this could be simplified. In total, I recommend:\n>>\n>>         if (lock.tempfile) {\n>>                 if (err)\n>>                         rollback_lock_file(&lock);\n>>                 else\n>>                         return write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>>         }\n>>         return err;\n> \n> FWIW, I also find that way of writing it easier to grok. Although,\n> rather than peeking at `lock.tempfile`, I suggest using\n> `is_lock_file_locked(&lock)`.\n> \n\nOK, this looks good to me.\n\n> Martin\n> \n\nAlban\n\n"},{"id":"413515","messageId":"411b68ad-dee5-5a19-ae94-c2b6a249161a@gmail.com","threadId":"53755","inReplyTo":"44c9189d-9d2f-c437-d0d6-9529708d2c99@gmail.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-01-05T23:20:33Z","receivedAt":"2021-01-05T23:21:57Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Le 05/01/2021 à 17:11, Derrick Stolee a écrit :\n> On 11/24/2020 6:53 AM, Alban Gruin wrote:\n>> +\n>>  \tpgm = argv[i++];\n>> +\tsetup_work_tree();\n>> +\n>> +\tif (!strcmp(pgm, \"git-merge-one-file\")) {\n> \n> This stood out to me as possibly fragile. What if we call the\n> non-dashed form \"git merge-one-file\"? Shouldn't we be doing so?\n> \n> Or, is this something that is handled higher in the builtin\n> machinery to take the non-dashed version and change it to the\n> dashed version for historical reasons?\n> \n\nWe had the same discussion with Phillip, who pointed out this previous\ndiscussion about this topic:\nhttps://lore.kernel.org/git/xmqqblv5kr9u.fsf@gitster-ct.c.googlers.com/\n\nSo, it's probably OK to do that.\n\n>> +\t\tmerge_action = merge_one_file_func;\n>> +\t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n>> +\t} else {\n>> +\t\tmerge_action = merge_one_file_spawn;\n>> +\t\tdata = (void *)pgm;\n>> +\t}\n>> +\n> \n> ...\n> \n>> +\tif (merge_action == merge_one_file_func) {\n> \n> nit: This made me think it would be better to check the 'lock'\n> itself to see if it was initialized or not. Perhaps\n> \n> \tif (lock.tempfile) {\n> \n> would be the appropriate way to check this?\n> \n> For now, this is equivalent behavior, but it might be helpful if\n> we add more cases that take the lock in the future.\n> \n>> +\t\tif (err) {\n>> +\t\t\trollback_lock_file(&lock);\n>> +\t\t\treturn err;\n>> +\t\t}\n>> +\n>> +\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n>>  \t}\n>>  \treturn err;\n> \n> nit: this could be simplified. In total, I recommend:\n> \n> \tif (lock.tempfile) {\n> \t\tif (err)\n> \t\t\trollback_lock_file(&lock);\n> \t\telse\n> \t\t\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n> \t}\n> \treturn err;\n> \n\nSure, looks better than mine.  :)\n\n> \n>>  }\n>> diff --git a/merge-strategies.c b/merge-strategies.c\n>> index 6f27e66dfe..542cefcf3d 100644\n>> --- a/merge-strategies.c\n>> +++ b/merge-strategies.c\n>> @@ -178,6 +178,18 @@ int merge_three_way(struct repository *r,\n>>  \treturn 0;\n>>  }\n>>  \n>> +int merge_one_file_func(struct repository *r,\n>> +\t\t\tconst struct object_id *orig_blob,\n>> +\t\t\tconst struct object_id *our_blob,\n>> +\t\t\tconst struct object_id *their_blob, const char *path,\n>> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>> +\t\t\tvoid *data)\n>> +{\n>> +\treturn merge_three_way(r,\n>> +\t\t\t       orig_blob, our_blob, their_blob, path,\n>> +\t\t\t       orig_mode, our_mode, their_mode);\n>> +}\n>> +\n> \n> Again, I don't recommend making this callback in the library. Instead, keep\n> it in the builtin and then use merge_three_way() which is in the library.\n> \n\nThis is not possible with this callback, as it will be used later by\nmerge_strategies_resolve() and indirectly by merge_strategies_octopus().\n\n>>  int merge_one_file_spawn(struct repository *r,\n>>  \t\t\t const struct object_id *orig_blob,\n>>  \t\t\t const struct object_id *our_blob,\n>> @@ -261,17 +273,22 @@ int merge_all_index(struct repository *r, int oneshot, int quiet,\n>>  \t\t    merge_fn fn, void *data)\n>>  {\n>>  \tint err = 0, ret;\n>> -\tunsigned int i;\n>> +\tunsigned int i, prev_nr;\n>>  \n>>  \tfor (i = 0; i < r->index->cache_nr; i++) {\n>>  \t\tconst struct cache_entry *ce = r->index->cache[i];\n>>  \t\tif (!ce_stage(ce))\n>>  \t\t\tcontinue;\n>>  \n>> +\t\tprev_nr = r->index->cache_nr;\n>>  \t\tret = merge_entry(r, quiet || oneshot, i, ce->name, &err, fn, data);\n>> -\t\tif (ret > 0)\n>> -\t\t\ti += ret - 1;\n>> -\t\telse if (ret == -1)\n>> +\t\tif (ret > 0) {\n>> +\t\t\t/* Don't bother handling an index that has\n>> +\t\t\t   grown, since merge_one_file_func() can't grow\n>> +\t\t\t   it, and merge_one_file_spawn() can't change\n>> +\t\t\t   it. */\n> \n> multi-line comment style is as follows:\n> \n> \t/*\n> \t * Don't bother handling an index that has\n> \t * grown, since merge_one_file_func() can't grow\n> \t * it, and merge_one_file_spawn() can't change it.\n> \t */\n> \n> Thanks,\n> -Stolee\n> \n\n"},{"id":"413522","messageId":"xmqqv9cax1le.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"411b68ad-dee5-5a19-ae94-c2b6a249161a@gmail.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-06T02:04:29Z","receivedAt":"2021-01-06T02:05:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> We had the same discussion with Phillip, who pointed out this previous\n> discussion about this topic:\n> https://lore.kernel.org/git/xmqqblv5kr9u.fsf@gitster-ct.c.googlers.com/\n>\n> So, it's probably OK to do that.\n\nThese days, there exists an optional installation option exists that\nwon't even install built-in commands in $GIT_EXEC_PATH, which\ninvalidates the assessment made in 2019 in the article you cited\nabove, so the code might still be OK, but the old justification no\nlonger would apply.\n\nIn any case, if two people who reviewed a patch found the same thing\nin it fishy, it is an indication that the reason why the apparently\nfishy code is OK needs to be better explained so that future readers\nof the code do not have to be puzzled about the same thing.\n\nThanks.\n\n"},{"id":"413784","messageId":"xmqqwnwnj4vh.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"30de2a29-a3fe-57f7-76b4-5d2bd3688635@gmail.com","subject":"Re: [PATCH v6 04/13] merge-one-file: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-08T06:54:10Z","receivedAt":"2021-01-08T06:55:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n>> If we were to do that, then I do not mind the repetition of 0, 1, 1\n>> too much.\n\nSorry, I think we will not need to see repetition of \"0, 1, 1\" if we\ntake the route I outlined above.\n\n> Okay.  Are we sure we want add_merge_result_to_index() inside\n> read-cache.c/cache.h?\n\nI wouldn't be surprised if you can find a better place, so please\ntry to see if there is one.  If the only user ends up being the\nmerge-one-file itself and nobody else, then it may be a better\nplace.  Or perhaps merge-strategies.c turns out to be a better place\nif other parts of the merge machinery can reuse it.\n\nThanks.\n"},{"id":"413975","messageId":"f7d7cc3b-b53d-ed48-8aa4-2b26a0ce7da3@gmail.com","threadId":"53755","inReplyTo":"xmqqv9cax1le.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-01-10T17:15:52Z","receivedAt":"2021-01-10T17:16:47Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nLe 06/01/2021 à 03:04, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> We had the same discussion with Phillip, who pointed out this previous\n>> discussion about this topic:\n>> https://lore.kernel.org/git/xmqqblv5kr9u.fsf@gitster-ct.c.googlers.com/\n>>\n>> So, it's probably OK to do that.\n> \n> These days, there exists an optional installation option exists that\n> won't even install built-in commands in $GIT_EXEC_PATH, which\n> invalidates the assessment made in 2019 in the article you cited\n> above, so the code might still be OK, but the old justification no\n> longer would apply.\n> \n> In any case, if two people who reviewed a patch found the same thing\n> in it fishy, it is an indication that the reason why the apparently\n> fishy code is OK needs to be better explained so that future readers\n> of the code do not have to be puzzled about the same thing.\n> \n> Thanks.\n> \n\nPerhaps we could try to check if the provided command exists (with\nlocate_in_PATH()), if it does, run it through merge_one_file_spawn(),\nelse, use merge_one_file_func()?\n\nAlban\n\n"},{"id":"413989","messageId":"xmqqzh1g8qhd.fsf@gitster.c.googlers.com","threadId":"53755","inReplyTo":"f7d7cc3b-b53d-ed48-8aa4-2b26a0ce7da3@gmail.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-10T20:51:58Z","receivedAt":"2021-01-10T20:53:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n>> These days, there exists an optional installation option exists that\n>> won't even install built-in commands in $GIT_EXEC_PATH, which\n>> invalidates the assessment made in 2019 in the article you cited\n>> above, so the code might still be OK, but the old justification no\n>> longer would apply.\n>> \n>> In any case, if two people who reviewed a patch found the same thing\n>> in it fishy, it is an indication that the reason why the apparently\n>> fishy code is OK needs to be better explained so that future readers\n>> of the code do not have to be puzzled about the same thing.\n>\n> Perhaps we could try to check if the provided command exists (with\n> locate_in_PATH()), if it does, run it through merge_one_file_spawn(),\n> else, use merge_one_file_func()?\n\nSo you think your current implementation will be broken if the \"no\ndashed git binary on disk\" installation option is used?\n\nI do not think \"first check if an on-disk command exists and use it,\notherwise check its name\" alone would work well in practice.  Both\nthe 'cat' example that appears in the manual page, and the typical\ninvocation of git-merge-one-file from merge-resolve:\n\n\tgit merge-index cat MM\n\tgit merge-index git-merge-one-file -a\n\nwould work just as well as before, but does not give you a way to\nbypass fork() for the latter.  And changing the order of checks\nwould mean the users won't have a way to override a buggy builtin\nimplementation of merge_one_file function.  Besides, using the name\nof the binary feels like a bad hack.  \n\nAs the invocation from merge-resolve is purely an internal matter,\nit may make more sense to introduce a new option and explicitly tell\nmerge-index that the command line is not asking for an external\nprogram to be spawned, e.g.\n\n\tgit merge-index --use=merge-one-file -a\n\nYou'd prepare a table of internally implemented \"take info on a\nsingle path that is being merged and give an automated resolution\"\nfunctions, which begins with a single entry that maps the string\n\"merge-one-file\" to your merge_one_file_func function.  Any value to\nthe \"--use\" option that names a function not in the table would\ncause an error.\n\nNote that in the above the \"table of functions\" is merely\nconceptual.  It is perfectly OK to implement the single entry table\nby codeflow (i.e. \"if (!strcmp()) ... else error();\").  But thinking\nin terms of \"a table of functions the user can choose from\" helps to\nform the right mental picture.\n\nHmm?\n"},{"id":"418526","messageId":"0371a283-61a4-b4d4-0909-5ce4b3cb2485@gmail.com","threadId":"53755","inReplyTo":"xmqqzh1g8qhd.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6 06/13] merge-index: don't fork if the requested program is `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-08T20:32:55Z","receivedAt":"2021-03-08T20:34:10Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nLe 10/01/2021 à 21:51, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>>> These days, there exists an optional installation option exists that\n>>> won't even install built-in commands in $GIT_EXEC_PATH, which\n>>> invalidates the assessment made in 2019 in the article you cited\n>>> above, so the code might still be OK, but the old justification no\n>>> longer would apply.\n>>>\n>>> In any case, if two people who reviewed a patch found the same thing\n>>> in it fishy, it is an indication that the reason why the apparently\n>>> fishy code is OK needs to be better explained so that future readers\n>>> of the code do not have to be puzzled about the same thing.\n>>\n>> Perhaps we could try to check if the provided command exists (with\n>> locate_in_PATH()), if it does, run it through merge_one_file_spawn(),\n>> else, use merge_one_file_func()?\n> \n> So you think your current implementation will be broken if the \"no\n> dashed git binary on disk\" installation option is used?\n> \n> I do not think \"first check if an on-disk command exists and use it,\n> otherwise check its name\" alone would work well in practice.  Both\n> the 'cat' example that appears in the manual page, and the typical\n> invocation of git-merge-one-file from merge-resolve:\n> \n> \tgit merge-index cat MM\n> \tgit merge-index git-merge-one-file -a\n> \n> would work just as well as before, but does not give you a way to\n> bypass fork() for the latter.  And changing the order of checks\n> would mean the users won't have a way to override a buggy builtin\n> implementation of merge_one_file function.  Besides, using the name\n> of the binary feels like a bad hack.  \n> \n> As the invocation from merge-resolve is purely an internal matter,\n> it may make more sense to introduce a new option and explicitly tell\n> merge-index that the command line is not asking for an external\n> program to be spawned, e.g.\n> \n> \tgit merge-index --use=merge-one-file -a\n> \n> You'd prepare a table of internally implemented \"take info on a\n> single path that is being merged and give an automated resolution\"\n> functions, which begins with a single entry that maps the string\n> \"merge-one-file\" to your merge_one_file_func function.  Any value to\n> the \"--use\" option that names a function not in the table would\n> cause an error.\n> \n> Note that in the above the \"table of functions\" is merely\n> conceptual.  It is perfectly OK to implement the single entry table\n> by codeflow (i.e. \"if (!strcmp()) ... else error();\").  But thinking\n> in terms of \"a table of functions the user can choose from\" helps to\n> form the right mental picture.\n> \n> Hmm?\n> \n\nYes, this should work.\n\nTo achieve this, I think I have to reorder this series a bit.\nCurrently, this is what it looks like:\n\n 1. convert git-merge-one-file;\n 2. libify git-merge-index, add the ability to call merge-one-file directly;\n 3. convert the resolve strategy;\n 4. convert the octopus strategy.\n\nAfter the reorder, the series would look like this:\n\n 1. libify git-merge-index, add `--use=merge-one-file', change\ngit-merge-resolve.sh, -octopus.sh, and t6060 to use this new parameter;\n 2. convert git-merge-one-file, add the ability for merge-index to call\nit directly;\n 3. convert the resolve strategy;\n 4. convert the octopus strategy.\n\nAlban\n\n"},{"id":"419555","messageId":"20210317204939.17890-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 01/15] t6407: modernise tests","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:25Z","receivedAt":"2021-03-17T20:57:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Some tests in t6407 uses a if/then/else to check if a command failed or\nnot, but we have the `test_must_fail' function to do it correctly for us\nnowadays.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6407-merge-binary.sh | 27 ++++++---------------------\n 1 file changed, 6 insertions(+), 21 deletions(-)\n\ndiff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh\nindex d4273f2575..bd2696367b 100755\n--- a/t/t6407-merge-binary.sh\n+++ b/t/t6407-merge-binary.sh\n@@ -8,7 +8,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n . ./test-lib.sh\n \n test_expect_success setup '\n-\n \tcat \"$TEST_DIRECTORY\"/test-binary-1.png >m &&\n \tgit add m &&\n \tgit ls-files -s | sed -e \"s/ 0\t/ 1\t/\" >E1 &&\n@@ -38,33 +37,19 @@ test_expect_success setup '\n '\n \n test_expect_success resolve '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s resolve main\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s resolve main &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_expect_success recursive '\n-\n \trm -f a* m* &&\n \tgit reset --hard anchor &&\n-\n-\tif git merge -s recursive main\n-\tthen\n-\t\techo Oops, should not have succeeded\n-\t\tfalse\n-\telse\n-\t\tgit ls-files -s >current\n-\t\ttest_cmp expect current\n-\tfi\n+\ttest_must_fail git merge -s recursive main &&\n+\tgit ls-files -s >current &&\n+\ttest_cmp expect current\n '\n \n test_done\n-- \n2.31.0\n\n"},{"id":"419565","messageId":"20210317204939.17890-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20201124115315.13311-1-alban.gruin@gmail.com","subject":"[PATCH v7 00/15] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:24Z","receivedAt":"2021-03-17T20:57:39Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In an effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThe first patch is not important to make the whole series work, but I\nmade this patch while working on it.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without changes.\n\nThis series is based on a5828ae6b5 (Git 2.31, 2021-03-15).  The tip is\ntagged as \"rewrite-merge-strategies-v7\" at https://github.com/agrn/git.\n\nChanges since v6:\n\n - The series has been rebased on Git 2.31.\n\n - The series has been reordered.  Now, all the work around merge-index\n   happens first to handle the case when git has been compiled with\n   SKIP_DASHED_BUILT_INS enabled.\n\n - Remove usage of the index in the new builtins and in merge-index.\n\n - Adapt t6407 to use the \"main\" branch instead of \"master\".\n\n - Move merge_one_file_spawn() from merge-strategies.c to\n   builtin/merge-index.c.\n\n - The functions extracted from merge-index and merge_three_way() now\n   take a `struct index_state *' instead of a `struct repository *'.\n\n - Introduce ADD_TO_INDEX_CACHEINFO_{INVALID_PATH,UNABLE_TO_ADD}.\n\n - Remove checkout_from_index(), and replace it by\n   add_merge_result_to_index(), a new function that calls\n   add_to_index_cacheinfo() and checkout_entry() at the same time.\n\n - Fix a case where a file deleted in both branches would result in a\n   failure in merge_three_way().  A test case has been added in t6060 to\n   check that the new version is correct.\n\n - Rename some variables in merge_strategies_octopus(), and change its\n   flow to make it more understandable.\n\n - Use CALLOC_ARRAY() in merge_strategies_octopus() instead of\n   xcalloc().\n\n - Change merge-resolve and merge-octopus to handle the case where they\n   are given an empty tree instead of a commit.\n\nAlban Gruin (15):\n  t6407: modernise tests\n  t6060: modify multiple files to expose a possible issue with\n    merge-index\n  t6060: add tests for removed files\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: drop the index\n  merge-index: add a new way to invoke `git-merge-one-file'\n  update-index: move add_cacheinfo() to read-cache.c\n  merge-one-file: rewrite in C\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" merge strategy without forking\n\n Documentation/git-merge-index.txt |   7 +-\n Makefile                          |   7 +-\n builtin.h                         |   3 +\n builtin/merge-index.c             | 119 ++++---\n builtin/merge-octopus.c           |  70 ++++\n builtin/merge-one-file.c          |  94 ++++++\n builtin/merge-recursive.c         |  16 +-\n builtin/merge-resolve.c           |  74 ++++\n builtin/merge.c                   |   7 +\n builtin/update-index.c            |  25 +-\n cache.h                           |  10 +-\n git-merge-octopus.sh              | 112 ------\n git-merge-one-file.sh             | 167 ---------\n git-merge-resolve.sh              |  54 ---\n git.c                             |   3 +\n merge-strategies.c                | 544 ++++++++++++++++++++++++++++++\n merge-strategies.h                |  39 +++\n merge.c                           |  12 +\n read-cache.c                      |  35 ++\n sequencer.c                       |  17 +-\n t/t6060-merge-index.sh            |  23 +-\n t/t6407-merge-binary.sh           |  27 +-\n t/t6415-merge-dir-to-symlink.sh   |   2 +-\n 23 files changed, 1001 insertions(+), 466 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v6:\n 1:  70d6507330 !  1:  dfe230bfce t6407: modernise tests\n    @@ Commit message\n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## t/t6407-merge-binary.sh ##\n    -@@ t/t6407-merge-binary.sh: test_description='ask merge-recursive to merge binary files'\n    +@@ t/t6407-merge-binary.sh: export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n      . ./test-lib.sh\n      \n      test_expect_success setup '\n    @@ t/t6407-merge-binary.sh: test_expect_success setup '\n      \trm -f a* m* &&\n      \tgit reset --hard anchor &&\n     -\n    --\tif git merge -s resolve master\n    +-\tif git merge -s resolve main\n     -\tthen\n     -\t\techo Oops, should not have succeeded\n     -\t\tfalse\n    @@ t/t6407-merge-binary.sh: test_expect_success setup '\n     -\t\tgit ls-files -s >current\n     -\t\ttest_cmp expect current\n     -\tfi\n    -+\ttest_must_fail git merge -s resolve master &&\n    ++\ttest_must_fail git merge -s resolve main &&\n     +\tgit ls-files -s >current &&\n     +\ttest_cmp expect current\n      '\n    @@ t/t6407-merge-binary.sh: test_expect_success setup '\n      \trm -f a* m* &&\n      \tgit reset --hard anchor &&\n     -\n    --\tif git merge -s recursive master\n    +-\tif git merge -s recursive main\n     -\tthen\n     -\t\techo Oops, should not have succeeded\n     -\t\tfalse\n    @@ t/t6407-merge-binary.sh: test_expect_success setup '\n     -\t\tgit ls-files -s >current\n     -\t\ttest_cmp expect current\n     -\tfi\n    -+\ttest_must_fail git merge -s recursive master &&\n    ++\ttest_must_fail git merge -s recursive main &&\n     +\tgit ls-files -s >current &&\n     +\ttest_cmp expect current\n      '\n 2:  25e9c47e41 =  2:  575e24685d t6060: modify multiple files to expose a possible issue with merge-index\n -:  ---------- >  3:  4f366ff363 t6060: add tests for removed files\n -:  ---------- >  4:  6af79a6b2d merge-index: libify merge_one_path() and merge_all()\n -:  ---------- >  5:  909ed66114 merge-index: drop the index\n -:  ---------- >  6:  1a8aba05bd merge-index: add a new way to invoke `git-merge-one-file'\n 3:  e7ea43c5ff !  7:  1f6635512c update-index: move add_cacheinfo() to read-cache.c\n    @@ builtin/update-index.c: static int process_path(const char *path, struct stat *s\n     -\tif (add_cache_entry(ce, option))\n     +\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n     +\t\t\t\t     allow_add, allow_replace, NULL);\n    -+\tif (res == -1)\n    -+\t\treturn res;\n    -+\tif (res == -2)\n    ++\tif (res == ADD_TO_INDEX_CACHEINFO_INVALID_PATH)\n    ++\t\treturn error(_(\"Invalid path '%s'\"), path);\n    ++\tif (res == ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD)\n      \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n      \t\t\t     path);\n     +\n    @@ cache.h: int remove_file_from_index(struct index_state *, const char *path);\n      int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n      int add_file_to_index(struct index_state *, const char *path, int flags);\n      \n    ++#define ADD_TO_INDEX_CACHEINFO_INVALID_PATH (-1)\n    ++#define ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD (-2)\n    ++\n     +int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n     +\t\t\t   const struct object_id *oid, const char *path,\n     +\t\t\t   int stage, int allow_add, int allow_replace,\n    -+\t\t\t   struct cache_entry **pce);\n    ++\t\t\t   struct cache_entry **ce_ret);\n     +\n      int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n      int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n    @@ read-cache.c: int add_index_entry(struct index_state *istate, struct cache_entry\n     +int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n     +\t\t\t   const struct object_id *oid, const char *path,\n     +\t\t\t   int stage, int allow_add, int allow_replace,\n    -+\t\t\t   struct cache_entry **pce)\n    ++\t\t\t   struct cache_entry **ce_ret)\n     +{\n     +\tint len, option;\n    -+\tstruct cache_entry *ce = NULL;\n    ++\tstruct cache_entry *ce;\n     +\n     +\tif (!verify_path(path, mode))\n    -+\t\treturn error(_(\"Invalid path '%s'\"), path);\n    ++\t\treturn ADD_TO_INDEX_CACHEINFO_INVALID_PATH;\n     +\n     +\tlen = strlen(path);\n     +\tce = make_empty_cache_entry(istate, len);\n    @@ read-cache.c: int add_index_entry(struct index_state *istate, struct cache_entry\n     +\n     +\tif (add_index_entry(istate, ce, option)) {\n     +\t\tdiscard_cache_entry(ce);\n    -+\t\treturn -2;\n    ++\t\treturn ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD;\n     +\t}\n     +\n    -+\tif (pce)\n    -+\t\t*pce = ce;\n    ++\tif (ce_ret)\n    ++\t\t*ce_ret = ce;\n     +\n     +\treturn 0;\n     +}\n 4:  284fc4227f !  8:  8755608f6d merge-one-file: rewrite in C\n    @@ Commit message\n         it did not because there was no regular file called `a/b'.  This test is\n         now marked as successful.\n     \n    +    This also teaches `merge-index' to call merge_three_way() (when invoked\n    +    with `--use=merge-one-file') without forking using a new callback,\n    +    merge_one_file_func().\n    +\n    +    To avoid any issue with a shrinking index because of the merge function\n    +    used (directly in the process or by forking), as described earlier, the\n    +    iterator of the loop of merge_all_index() is increased by the number of\n    +    entries with the same name, minus the difference between the number of\n    +    entries in the index before and after the merge.\n    +\n    +    This should handle a shrinking index correctly, but could lead to issues\n    +    with a growing index.  However, this case is not treated, as there is no\n    +    callback that can produce such a case.\n    +\n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## Makefile ##\n    @@ Makefile: SCRIPT_SH += git-bisect.sh\n      SCRIPT_SH += git-merge-resolve.sh\n      SCRIPT_SH += git-mergetool.sh\n      SCRIPT_SH += git-quiltimport.sh\n    -@@ Makefile: LIB_OBJS += match-trees.o\n    - LIB_OBJS += mem-pool.o\n    - LIB_OBJS += merge-blobs.o\n    - LIB_OBJS += merge-recursive.o\n    -+LIB_OBJS += merge-strategies.o\n    - LIB_OBJS += merge.o\n    - LIB_OBJS += mergesort.o\n    - LIB_OBJS += midx.o\n     @@ Makefile: BUILTIN_OBJS += builtin/mailsplit.o\n      BUILTIN_OBJS += builtin/merge-base.o\n      BUILTIN_OBJS += builtin/merge-file.o\n    @@ builtin.h: int cmd_merge_base(int argc, const char **argv, const char *prefix);\n      int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n      int cmd_mktag(int argc, const char **argv, const char *prefix);\n     \n    + ## builtin/merge-index.c ##\n    +@@ builtin/merge-index.c: static int merge_one_file_spawn(struct index_state *istate,\n    + int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    + {\n    + \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n    +-\tmerge_fn merge_action = merge_one_file_spawn;\n    ++\tmerge_fn merge_action;\n    + \tstruct lock_file lock = LOCK_INIT;\n    + \tstruct repository *r = the_repository;\n    + \tconst char *use_internal = NULL;\n    +@@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    + \n    + \tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n    + \t\tif (!strcmp(use_internal, \"merge-one-file\"))\n    +-\t\t\tpgm = \"git-merge-one-file\";\n    ++\t\t\tmerge_action = merge_one_file_func;\n    + \t\telse\n    + \t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n    +-\t}\n    ++\n    ++\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    ++\t} else\n    ++\t\tmerge_action = merge_one_file_spawn;\n    + \n    + \tfor (; i < argc; i++) {\n    + \t\tconst char *arg = argv[i];\n    +\n      ## builtin/merge-one-file.c (new) ##\n     @@\n     +/*\n    @@ builtin/merge-one-file.c (new)\n     + * that might change the tree layout.\n     + */\n     +\n    -+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     +#include \"cache.h\"\n     +#include \"builtin.h\"\n     +#include \"lockfile.h\"\n    @@ builtin/merge-one-file.c (new)\n     +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n     +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n     +\tstruct lock_file lock = LOCK_INIT;\n    ++\tstruct repository *r = the_repository;\n     +\n     +\tif (argc != 8)\n     +\t\tusage(builtin_merge_one_file_usage);\n     +\n    -+\tif (read_cache() < 0)\n    ++\tif (repo_read_index(r) < 0)\n     +\t\tdie(\"invalid index\");\n     +\n    -+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n    ++\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n     +\n     +\tif (!get_oid_hex(argv[1], &orig_blob)) {\n     +\t\tp_orig_blob = &orig_blob;\n    @@ builtin/merge-one-file.c (new)\n     +\tif (ret)\n     +\t\treturn ret;\n     +\n    -+\tret = merge_three_way(the_repository, p_orig_blob, p_our_blob, p_their_blob,\n    ++\tret = merge_three_way(r->index, p_orig_blob, p_our_blob, p_their_blob,\n     +\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n     +\n     +\tif (ret) {\n    @@ builtin/merge-one-file.c (new)\n     +\t\treturn !!ret;\n     +\t}\n     +\n    -+\treturn write_locked_index(&the_index, &lock, COMMIT_LOCK);\n    ++\treturn write_locked_index(r->index, &lock, COMMIT_LOCK);\n     +}\n     \n      ## git-merge-one-file.sh (deleted) ##\n    @@ git.c: 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     \n    - ## merge-strategies.c (new) ##\n    + ## merge-strategies.c ##\n     @@\n    -+#include \"cache.h\"\n    + #include \"cache.h\"\n     +#include \"dir.h\"\n    -+#include \"merge-strategies.h\"\n    + #include \"merge-strategies.h\"\n     +#include \"xdiff-interface.h\"\n     +\n    -+static int checkout_from_index(struct index_state *istate, const char *path,\n    -+\t\t\t       struct cache_entry *ce)\n    ++static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n    ++\t\t\t\t     const struct object_id *oid, const char *path,\n    ++\t\t\t\t     int checkout)\n     +{\n    -+\tstruct checkout state = CHECKOUT_INIT;\n    ++\tstruct cache_entry *ce;\n    ++\tint res;\n     +\n    -+\tstate.istate = istate;\n    -+\tstate.force = 1;\n    -+\tstate.base_dir = \"\";\n    -+\tstate.base_dir_len = 0;\n    ++\tres = add_to_index_cacheinfo(istate, mode, oid, path, 0, 1, 1, &ce);\n    ++\tif (res == -1)\n    ++\t\treturn error(_(\"Invalid path '%s'\"), path);\n    ++\telse if (res == -2)\n    ++\t\treturn -1;\n    ++\n    ++\tif (checkout) {\n    ++\t\tstruct checkout state = CHECKOUT_INIT;\n    ++\n    ++\t\tstate.istate = istate;\n    ++\t\tstate.force = 1;\n    ++\t\tstate.base_dir = \"\";\n    ++\t\tstate.base_dir_len = 0;\n    ++\n    ++\t\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n    ++\t\t\treturn error(_(\"%s: cannot checkout file\"), path);\n    ++\t}\n     +\n    -+\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n    -+\t\treturn error(_(\"%s: cannot checkout file\"), path);\n     +\treturn 0;\n     +}\n     +\n    @@ merge-strategies.c (new)\n     +\t\t\t\t  const struct object_id *their_blob, const char *path,\n     +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n     +{\n    -+\tif ((our_blob && orig_mode != our_mode) ||\n    -+\t    (their_blob && orig_mode != their_mode))\n    ++\tif ((!our_blob && orig_mode != their_mode) ||\n    ++\t    (!their_blob && orig_mode != our_mode))\n     +\t\treturn error(_(\"File %s deleted on one branch but had its \"\n     +\t\t\t       \"permissions changed on the other.\"), path);\n     +\n    @@ merge-strategies.c (new)\n     +\treturn add_file_to_index(istate, path, 0);\n     +}\n     +\n    -+int merge_three_way(struct repository *r,\n    ++int merge_three_way(struct index_state *istate,\n     +\t\t    const struct object_id *orig_blob,\n     +\t\t    const struct object_id *our_blob,\n     +\t\t    const struct object_id *their_blob, const char *path,\n     +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n     +{\n     +\tif (orig_blob &&\n    -+\t    ((!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n    ++\t    ((!our_blob && !their_blob) ||\n    ++\t     (!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n     +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n     +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n    -+\t\treturn merge_one_file_deleted(r->index, our_blob, their_blob, path,\n    ++\t\treturn merge_one_file_deleted(istate, our_blob, their_blob, path,\n     +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n     +\t} else if (!orig_blob && our_blob && !their_blob) {\n     +\t\t/*\n    -+\t\t * Added in one.  The other side did not add and we\n    ++\t\t * Added in ours.  The other side did not add and we\n     +\t\t * added so there is nothing to be done, except making\n     +\t\t * the path merged.\n     +\t\t */\n    -+\t\treturn add_to_index_cacheinfo(r->index, our_mode, our_blob,\n    -+\t\t\t\t\t      path, 0, 1, 1, NULL);\n    ++\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 0);\n     +\t} else if (!orig_blob && !our_blob && their_blob) {\n    -+\t\tstruct cache_entry *ce;\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n     +\n     +\t\tif (file_exists(path))\n     +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n     +\n    -+\t\tif (add_to_index_cacheinfo(r->index, their_mode, their_blob,\n    -+\t\t\t\t\t   path, 0, 1, 1, &ce))\n    -+\t\t\treturn -1;\n    -+\t\treturn checkout_from_index(r->index, path, ce);\n    ++\t\treturn add_merge_result_to_index(istate, their_mode, their_blob, path, 1);\n     +\t} else if (!orig_blob && our_blob && their_blob &&\n     +\t\t   oideq(our_blob, their_blob)) {\n    -+\t\tstruct cache_entry *ce;\n    -+\n     +\t\t/* Added in both, identically (check for same permissions). */\n     +\t\tif (our_mode != their_mode)\n     +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n    @@ merge-strategies.c (new)\n     +\n     +\t\tprintf(_(\"Adding %s\\n\"), path);\n     +\n    -+\t\tif (add_to_index_cacheinfo(r->index, our_mode, our_blob,\n    -+\t\t\t\t\t   path, 0, 1, 1, &ce))\n    -+\t\t\treturn -1;\n    -+\t\treturn checkout_from_index(r->index, path, ce);\n    ++\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 1);\n     +\t} else if (our_blob && their_blob) {\n     +\t\t/* Modified in both, but differently. */\n    -+\t\treturn do_merge_one_file(r->index,\n    ++\t\treturn do_merge_one_file(istate,\n     +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n     +\t\t\t\t\t orig_mode, our_mode, their_mode);\n     +\t} else {\n    @@ merge-strategies.c (new)\n     +\n     +\treturn 0;\n     +}\n    ++\n    ++int merge_one_file_func(struct index_state *istate,\n    ++\t\t\tconst struct object_id *orig_blob,\n    ++\t\t\tconst struct object_id *our_blob,\n    ++\t\t\tconst struct object_id *their_blob, const char *path,\n    ++\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\tvoid *data)\n    ++{\n    ++\treturn merge_three_way(istate,\n    ++\t\t\t       orig_blob, our_blob, their_blob, path,\n    ++\t\t\t       orig_mode, our_mode, their_mode);\n    ++}\n    + \n    + static int merge_entry(struct index_state *istate, int quiet, unsigned int pos,\n    + \t\t       const char *path, int *err, merge_fn fn, void *data)\n    +@@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    + \t\t    merge_fn fn, void *data)\n    + {\n    + \tint err = 0, ret;\n    +-\tunsigned int i;\n    ++\tunsigned int i, prev_nr;\n    + \n    + \tfor (i = 0; i < istate->cache_nr; i++) {\n    + \t\tconst struct cache_entry *ce = istate->cache[i];\n    + \t\tif (!ce_stage(ce))\n    + \t\t\tcontinue;\n    + \n    ++\t\tprev_nr = istate->cache_nr;\n    + \t\tret = merge_entry(istate, quiet || oneshot, i, ce->name, &err, fn, data);\n    +-\t\tif (ret > 0)\n    +-\t\t\ti += ret - 1;\n    +-\t\telse if (ret == -1)\n    ++\t\tif (ret > 0) {\n    ++\t\t\t/*\n    ++\t\t\t * Don't bother handling an index that has\n    ++\t\t\t * grown, since merge_one_file_func() can't grow\n    ++\t\t\t * it, and merge_one_file_spawn() can't change\n    ++\t\t\t * it.\n    ++\t\t\t */\n    ++\t\t\ti += ret - (prev_nr - istate->cache_nr) - 1;\n    ++\t\t} else if (ret == -1)\n    + \t\t\treturn -1;\n    + \n    + \t\tif (err && !oneshot)\n     \n    - ## merge-strategies.h (new) ##\n    + ## merge-strategies.h ##\n     @@\n    -+#ifndef MERGE_STRATEGIES_H\n    -+#define MERGE_STRATEGIES_H\n    -+\n    -+#include \"object.h\"\n    -+\n    -+int merge_three_way(struct repository *r,\n    + \n    + #include \"object.h\"\n    + \n    ++int merge_three_way(struct index_state *istate,\n     +\t\t    const struct object_id *orig_blob,\n     +\t\t    const struct object_id *our_blob,\n     +\t\t    const struct object_id *their_blob, const char *path,\n     +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n     +\n    -+#endif /* MERGE_STRATEGIES_H */\n    + typedef int (*merge_fn)(struct index_state *istate,\n    + \t\t\tconst struct object_id *orig_blob,\n    + \t\t\tconst struct object_id *our_blob,\n    +@@ merge-strategies.h: typedef int (*merge_fn)(struct index_state *istate,\n    + \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    + \t\t\tvoid *data);\n    + \n    ++int merge_one_file_func(struct index_state *istate,\n    ++\t\t\tconst struct object_id *orig_blob,\n    ++\t\t\tconst struct object_id *our_blob,\n    ++\t\t\tconst struct object_id *their_blob, const char *path,\n    ++\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\tvoid *data);\n    ++\n    + int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    + \t\t     const char *path, merge_fn fn, void *data);\n    + int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    +\n    + ## t/t6060-merge-index.sh ##\n    +@@ t/t6060-merge-index.sh: test_expect_success 'merge-one-file fails without a work tree' '\n    + \t(cd bare.git &&\n    + \t GIT_INDEX_FILE=$PWD/merge.index &&\n    + \t export GIT_INDEX_FILE &&\n    +-\t test_must_fail git merge-index git-merge-one-file -a\n    ++\t test_must_fail git merge-index --use=merge-one-file -a\n    + \t)\n    + '\n    + \n     \n      ## t/t6415-merge-dir-to-symlink.sh ##\n     @@ t/t6415-merge-dir-to-symlink.sh: test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n 5:  54abee902f <  -:  ---------- merge-index: libify merge_one_path() and merge_all()\n 6:  acaf100edd <  -:  ---------- merge-index: don't fork if the requested program is `git-merge-one-file'\n 7:  9a9e3faeff !  9:  3ecf49a8ac merge-resolve: rewrite in C\n    @@ builtin/merge-resolve.c (new)\n     + * Resolve two trees, using enhanced multi-base read-tree.\n     + */\n     +\n    -+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     +#include \"cache.h\"\n     +#include \"builtin.h\"\n     +#include \"merge-strategies.h\"\n    @@ builtin/merge-resolve.c (new)\n     +\tconst char *head = NULL;\n     +\tstruct commit_list *bases = NULL, *remote = NULL;\n     +\tstruct commit_list **next_base = &bases;\n    ++\tstruct repository *r = the_repository;\n     +\n     +\tif (argc < 5)\n     +\t\tusage(builtin_merge_resolve_usage);\n     +\n     +\tsetup_work_tree();\n    -+\tif (read_cache() < 0)\n    ++\tif (repo_read_index(r) < 0)\n     +\t\tdie(\"invalid index\");\n     +\n     +\t/*\n    @@ builtin/merge-resolve.c (new)\n     +\t\t\tif (get_oid(argv[i], &oid))\n     +\t\t\t\tdie(\"object %s not found.\", argv[i]);\n     +\n    -+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n    ++\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n    ++\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n     +\n     +\t\t\tif (sep_seen)\n     +\t\t\t\tcommit_list_insert(commit, &remote);\n    @@ builtin/merge-resolve.c (new)\n     +\tif (!bases)\n     +\t\treturn 2;\n     +\n    -+\treturn merge_strategies_resolve(the_repository, bases, head, remote);\n    ++\treturn merge_strategies_resolve(r, bases, head, remote);\n     +}\n     \n      ## git-merge-resolve.sh (deleted) ##\n    @@ git-merge-resolve.sh (deleted)\n     -\texit 0\n     -else\n     -\techo \"Simple merge failed, trying Automatic merge.\"\n    --\tif git merge-index -o git-merge-one-file -a\n    +-\tif git merge-index -o --use=merge-one-file -a\n     -\tthen\n     -\t\texit 0\n     -\telse\n    @@ merge-strategies.c\n      #include \"dir.h\"\n     +#include \"lockfile.h\"\n      #include \"merge-strategies.h\"\n    - #include \"run-command.h\"\n     +#include \"unpack-trees.h\"\n      #include \"xdiff-interface.h\"\n      \n    - static int checkout_from_index(struct index_state *istate, const char *path,\n    -@@ merge-strategies.c: int merge_all_index(struct repository *r, int oneshot, int quiet,\n    + static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n    +@@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n      \n      \treturn err;\n      }\n    @@ merge-strategies.c: int merge_all_index(struct repository *r, int oneshot, int q\n     +\n     +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\tret = merge_all_index(r, 1, 0, merge_one_file_func, NULL);\n    ++\t\tret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n     +\n     +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n     +\t\treturn !!ret;\n    @@ merge-strategies.h\n     +#include \"commit.h\"\n      #include \"object.h\"\n      \n    - int merge_three_way(struct repository *r,\n    -@@ merge-strategies.h: int merge_index_path(struct repository *r, int oneshot, int quiet,\n    - int merge_all_index(struct repository *r, int oneshot, int quiet,\n    + int merge_three_way(struct index_state *istate,\n    +@@ merge-strategies.h: int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    + int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n      \t\t    merge_fn fn, void *data);\n      \n     +int merge_strategies_resolve(struct repository *r,\n 8:  359346229c = 10:  615b04d417 merge-recursive: move better_branch_name() to merge.c\n 9:  4dff780212 ! 11:  a6ece04f3d merge-octopus: rewrite in C\n    @@ builtin/merge-octopus.c (new)\n     + * Resolve two or more trees.\n     + */\n     +\n    -+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     +#include \"cache.h\"\n     +#include \"builtin.h\"\n     +#include \"commit.h\"\n    @@ builtin/merge-octopus.c (new)\n     +\tstruct commit_list *bases = NULL, *remotes = NULL;\n     +\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n     +\tconst char *head_arg = NULL;\n    ++\tstruct repository *r = the_repository;\n     +\n     +\tif (argc < 5)\n     +\t\tusage(builtin_merge_octopus_usage);\n     +\n     +\tsetup_work_tree();\n    -+\tif (read_cache() < 0)\n    ++\tif (repo_read_index(r) < 0)\n     +\t\tdie(\"invalid index\");\n     +\n     +\t/*\n    @@ builtin/merge-octopus.c (new)\n     +\t\t\tif (get_oid(argv[i], &oid))\n     +\t\t\t\tdie(\"object %s not found.\", argv[i]);\n     +\n    -+\t\t\tcommit = lookup_commit_or_die(&oid, argv[i]);\n    ++\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n    ++\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n     +\n     +\t\t\tif (sep_seen)\n     +\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n    @@ builtin/merge-octopus.c (new)\n     +\tif (commit_list_count(remotes) < 2)\n     +\t\treturn 2;\n     +\n    -+\treturn merge_strategies_octopus(the_repository, bases, head_arg, remotes);\n    ++\treturn merge_strategies_octopus(r, bases, head_arg, remotes);\n     +}\n     \n      ## git-merge-octopus.sh (deleted) ##\n    @@ git-merge-octopus.sh (deleted)\n     -\tif test $? -ne 0\n     -\tthen\n     -\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n    --\t\tgit merge-index -o git-merge-one-file -a ||\n    +-\t\tgit merge-index -o --use=merge-one-file -a ||\n     -\t\tOCTOPUS_FAILURE=1\n     -\t\tnext=$(git write-tree 2>/dev/null)\n     -\tfi\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t\t    struct tree **reference_tree)\n     +{\n     +\tstruct tree_desc t[MAX_UNPACK_TREES];\n    -+\tstruct commit_list *j;\n    ++\tstruct commit_list *i;\n     +\tint nr = 0, ret = 0;\n     +\n     +\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n     +\n    -+\tfor (j = common; j; j = j->next) {\n    -+\t\tstruct tree *tree = repo_get_commit_tree(r, j->item);\n    ++\tfor (i = common; i; i = i->next) {\n    ++\t\tstruct tree *tree = repo_get_commit_tree(r, i->item);\n     +\t\tif (add_tree(tree, t + (nr++)))\n     +\t\t\treturn -1;\n     +\t}\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\tif (add_tree(current_tree, t + (nr++)))\n     +\t\treturn -1;\n     +\tif (fast_forward(r, t, nr, 1))\n    -+\t\treturn -1;\n    ++\t\treturn 2;\n     +\n     +\tif (write_tree(r, reference_tree)) {\n     +\t\tstruct lock_file lock = LOCK_INIT;\n     +\n     +\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\tret = merge_all_index(r, 1, 0, merge_one_file_func, NULL);\n    ++\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, NULL);\n     +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n     +\n     +\t\twrite_tree(r, reference_tree);\n     +\t}\n     +\n    -+\treturn ret ? -2 : 0;\n    ++\treturn ret;\n     +}\n     +\n     +int merge_strategies_octopus(struct repository *r,\n     +\t\t\t     struct commit_list *bases, const char *head_arg,\n     +\t\t\t     struct commit_list *remotes)\n     +{\n    -+\tint ff_merge = 1, ret = 0, references = 1;\n    -+\tstruct commit **reference_commit, *head_commit;\n    ++\tint ff_merge = 1, ret = 0, nr_references = 1;\n    ++\tstruct commit **reference_commits, *head_commit;\n     +\tstruct tree *reference_tree, *head_tree;\n     +\tstruct commit_list *i;\n     +\tstruct object_id head;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\treturn 2;\n     +\t}\n     +\n    -+\treference_commit = xcalloc(commit_list_count(remotes) + 1,\n    -+\t\t\t\t   sizeof(struct commit *));\n    -+\treference_commit[0] = head_commit;\n    ++\tCALLOC_ARRAY(reference_commits, commit_list_count(remotes) + 1);\n    ++\treference_commits[0] = head_commit;\n     +\treference_tree = head_tree;\n     +\n     +\tfor (i = remotes; i && i->item; i = i->next) {\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\tstruct object_id *oid = &c->object.oid;\n     +\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n     +\t\tstruct commit_list *common, *j;\n    -+\t\tchar *branch_name;\n    -+\t\tint k = 0, up_to_date = 0;\n    -+\n    -+\t\tif (ret) {\n    -+\t\t\t/*\n    -+\t\t\t * We allow only last one to have a\n    -+\t\t\t * hand-resolvable conflicts.  Last round failed\n    -+\t\t\t * and we still had a head to merge.\n    -+\t\t\t */\n    -+\t\t\tputs(_(\"Automated merge did not work.\"));\n    -+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n    -+\n    -+\t\t\tfree(reference_commit);\n    -+\t\t\treturn 2;\n    -+\t\t}\n    -+\n    -+\t\tbranch_name = merge_get_better_branch_name(oid_to_hex(oid));\n    -+\t\tcommon = get_merge_bases_many(c, references, reference_commit);\n    ++\t\tchar *branch_name = merge_get_better_branch_name(oid_to_hex(oid));\n    ++\t\tint up_to_date = 0;\n     +\n    ++\t\tcommon = repo_get_merge_bases_many(r, c, nr_references, reference_commits);\n     +\t\tif (!common) {\n     +\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n     +\n     +\t\t\tfree(branch_name);\n     +\t\t\tfree_commit_list(common);\n    -+\t\t\tfree(reference_commit);\n    ++\t\t\tfree(reference_commits);\n     +\n     +\t\t\treturn 2;\n     +\t\t}\n     +\n    -+\t\tfor (j = common; j && !(up_to_date || !ff_merge); j = j->next) {\n    ++\t\tfor (j = common; j && !up_to_date && ff_merge; j = j->next) {\n     +\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n     +\n    -+\t\t\tif (k < references)\n    -+\t\t\t\tff_merge &= oideq(&j->item->object.oid, &reference_commit[k++]->object.oid);\n    ++\t\t\tif (!j->next &&\n    ++\t\t\t    !oideq(&j->item->object.oid,\n    ++\t\t\t\t   &reference_commits[nr_references - 1]->object.oid))\n    ++\t\t\t\tff_merge = 0;\n     +\t\t}\n     +\n     +\t\tif (up_to_date) {\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\tif (ff_merge) {\n     +\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n     +\t\t\t\t\t\t   current_tree, &reference_tree);\n    -+\t\t\treferences = 0;\n    ++\t\t\tnr_references = 0;\n     +\t\t} else {\n     +\t\t\tret = octopus_do_merge(r, branch_name, common,\n     +\t\t\t\t\t       current_tree, &reference_tree);\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\tfree(branch_name);\n     +\t\tfree_commit_list(common);\n     +\n    -+\t\tif (ret == -1)\n    ++\t\tif (ret == -1 || ret == 2)\n     +\t\t\tbreak;\n    ++\t\telse if (ret && i->next) {\n    ++\t\t\t/*\n    ++\t\t\t * We allow only last one to have a\n    ++\t\t\t * hand-resolvable conflicts.  Last round failed\n    ++\t\t\t * and we still had a head to merge.\n    ++\t\t\t */\n    ++\t\t\tputs(_(\"Automated merge did not work.\"));\n    ++\t\t\tputs(_(\"Should not be doing an octopus.\"));\n     +\n    -+\t\treference_commit[references++] = c;\n    ++\t\t\tfree(reference_commits);\n    ++\t\t\treturn 2;\n    ++\t\t}\n    ++\n    ++\t\treference_commits[nr_references++] = c;\n     +\t}\n     +\n    -+\tfree(reference_commit);\n    ++\tfree(reference_commits);\n     +\treturn ret;\n     +}\n     \n      ## merge-strategies.h ##\n    -@@ merge-strategies.h: int merge_all_index(struct repository *r, int oneshot, int quiet,\n    +@@ merge-strategies.h: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n      int merge_strategies_resolve(struct repository *r,\n      \t\t\t     struct commit_list *bases, const char *head_arg,\n      \t\t\t     struct commit_list *remote);\n10:  76f02b4531 = 12:  cc1500147b merge: use the \"resolve\" strategy without forking\n11:  c9e0a38d0f = 13:  ec3dc3b81e merge: use the \"octopus\" strategy without forking\n12:  5b595efa46 = 14:  e7dc4a15d4 sequencer: use the \"resolve\" strategy without forking\n13:  7eb0f13442 = 15:  34280dd82d sequencer: use the \"octopus\" merge strategy without forking\n-- \n2.31.0\n\n"},{"id":"419556","messageId":"20210317204939.17890-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 02/15] t6060: modify multiple files to expose a possible issue with merge-index","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:26Z","receivedAt":"2021-03-17T20:57:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Currently, merge-index iterates over every index entry, skipping stage0\nentries.  It will then count how many entries following the current one\nhave the same name, then fork to do the merge.  It will then increase\nthe iterator by the number of entries to skip them.  This behaviour is\ncorrect, as even if the subprocess modifies the index, merge-index does\nnot reload it at all.\n\nBut when it will be rewritten to use a function, the index it will use\nwill be modified and may shrink when a conflict happens or if a file is\nremoved, so we have to be careful to handle such cases.\n\nHere is an example:\n\n *    Merge branches, file1 and file2 are trivially mergeable.\n |\\\n | *  Modifies file1 and file2.\n * |  Modifies file1 and file2.\n |/\n *    Adds file1 and file2.\n\nWhen the merge happens, the index will look like that:\n\n i -> 0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n      3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\nmerge-index handles `file1' first.  As it appears 3 times after the\niterator, it is merged.  The index is now stale, `i' is increased by 3,\nand the index now looks like this:\n\n      0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n i -> 3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\n`file2' appears three times too, so it is merged.\n\nWith a naive rewrite, the index would look like this:\n\n      0. file1 (stage0)\n      1. file2 (stage1)\n      2. file2 (stage2)\n i -> 3. file2 (stage3)\n\n`file2' appears once at the iterator or after, so it will be added,\n_not_ merged.  Which is wrong.\n\nA naive rewrite would lead to unproperly merged files, or even files not\nhandled at all.\n\nThis changes t6060 to reproduce this case, by creating 2 files instead\nof 1, to check the correctness of the soon-to-be-rewritten merge-index.\nThe files are identical, which is not really important -- the factors\nthat could trigger this issue are that they should be separated by at\nmost one entry in the index, and that the first one in the index should\nbe trivially mergeable.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6060-merge-index.sh | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex ddf34f0115..9e15ceb957 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -7,16 +7,19 @@ test_expect_success 'setup diverging branches' '\n \tfor i in 1 2 3 4 5 6 7 8 9 10; do\n \t\techo $i\n \tdone >file &&\n-\tgit add file &&\n+\tcp file file2 &&\n+\tgit add file file2 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n \tsed s/10/ten/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m ten &&\n \tgit tag ten\n '\n@@ -35,8 +38,11 @@ ten\n EOF\n \n test_expect_success 'read-tree does not resolve content merge' '\n+\tcat >expect <<-\\EOF &&\n+\tfile\n+\tfile2\n+\tEOF\n \tgit read-tree -i -m base ten two &&\n-\techo file >expect &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_cmp expect unmerged\n '\n-- \n2.31.0\n\n"},{"id":"419557","messageId":"20210317204939.17890-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 04/15] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:28Z","receivedAt":"2021-03-17T20:57:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves and renames merge_one_path(), merge_all(), and\ntheir helpers to merge-strategies.c.  They also take a callback to\ndictate what they should do for each file.  For now, to preserve the\nbehaviour of `merge-index', only one callback, launching a new process,\nis defined.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile              |  1 +\n builtin/merge-index.c | 90 +++++++++++++++----------------------------\n merge-strategies.c    | 75 ++++++++++++++++++++++++++++++++++++\n merge-strategies.h    | 18 +++++++++\n 4 files changed, 125 insertions(+), 59 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex dfb0f1000f..1b1dc49e86 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -913,6 +913,7 @@ LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-ort-wrappers.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\n LIB_OBJS += midx.o\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 38ea6ad6ca..70f440d9a0 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,74 +1,43 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"merge-strategies.h\"\n #include \"run-command.h\"\n \n static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n \n-static int merge_entry(int pos, const char *path)\n+static int merge_one_file_spawn(struct index_state *istate,\n+\t\t\t\tconst struct object_id *orig_blob,\n+\t\t\t\tconst struct object_id *our_blob,\n+\t\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t\tvoid *data)\n {\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n+\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n+\tchar modes[3][10] = {{0}};\n+\tconst char *arguments[] = { pgm, oids[0], oids[1], oids[2],\n+\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n \n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n+\tif (orig_blob) {\n+\t\toid_to_hex_r(oids[0], orig_blob);\n+\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n \t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n \n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n+\tif (our_blob) {\n+\t\toid_to_hex_r(oids[1], our_blob);\n+\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n+\t}\n \n-static void merge_all(void)\n-{\n-\tint i;\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n+\tif (their_blob) {\n+\t\toid_to_hex_r(oids[2], their_blob);\n+\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n \t}\n+\n+\treturn run_command_v_opt(arguments, 0);\n }\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -89,7 +58,9 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -98,14 +69,15 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t       merge_one_file_spawn, NULL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t\tmerge_one_file_spawn, NULL);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n+\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..c80f964612\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,75 @@\n+#include \"cache.h\"\n+#include \"merge-strategies.h\"\n+\n+static int merge_entry(struct index_state *istate, int quiet, unsigned int pos,\n+\t\t       const char *path, int *err, merge_fn fn, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (fn(istate, oids[0], oids[1], oids[2], path,\n+\t       modes[0], modes[1], modes[2], data)) {\n+\t\tif (!quiet)\n+\t\t\terror(_(\"Merge program failed\"));\n+\t\t(*err)++;\n+\t}\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret, err = 0;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, quiet || oneshot, -pos - 1, path, &err, fn, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (err)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data)\n+{\n+\tint err = 0, ret;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, quiet || oneshot, i, ce->name, &err, fn, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\n+\t\tif (err && !oneshot)\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..88f476f170\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,18 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+typedef int (*merge_fn)(struct index_state *istate,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data);\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data);\n+\n+#endif /* MERGE_STRATEGIES_H */\n-- \n2.31.0\n\n"},{"id":"419558","messageId":"20210317204939.17890-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 05/15] merge-index: drop the index","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:29Z","receivedAt":"2021-03-17T20:57:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In an effort to reduce the usage of the global index throughout the\ncodebase, this removes references to it in `git merge-index'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 9 +++++----\n 1 file changed, 5 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 70f440d9a0..49ddf3f9cd 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n@@ -38,6 +37,7 @@ static int merge_one_file_spawn(struct index_state *istate,\n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tstruct repository *r = the_repository;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -47,7 +47,8 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tif (argc < 3)\n \t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n \n-\tread_cache();\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n \n \ti = 1;\n \tif (!strcmp(argv[i], \"-o\")) {\n@@ -69,13 +70,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n+\t\t\t\terr |= merge_all_index(r->index, one_shot, quiet,\n \t\t\t\t\t\t       merge_one_file_spawn, NULL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n+\t\terr |= merge_index_path(r->index, one_shot, quiet, arg,\n \t\t\t\t\tmerge_one_file_spawn, NULL);\n \t}\n \n-- \n2.31.0\n\n"},{"id":"419559","messageId":"20210317204939.17890-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 03/15] t6060: add tests for removed files","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:27Z","receivedAt":"2021-03-17T20:57:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Until now, t6060 did not not check git-mere-one-file's behaviour when a\nfile is deleted in a branch.  To avoid regressions on this during the\nconversion, this adds a new file, `file3', in the commit tagged as`base', and\ndeletes it in the commit tagged as `two'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6060-merge-index.sh | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 9e15ceb957..0cbd8a1f7f 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -8,12 +8,14 @@ test_expect_success 'setup diverging branches' '\n \t\techo $i\n \tdone >file &&\n \tcp file file2 &&\n-\tgit add file file2 &&\n+\tcp file file3 &&\n+\tgit add file file2 file3 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n \tcp file file2 &&\n+\tgit rm file3 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n@@ -41,6 +43,7 @@ test_expect_success 'read-tree does not resolve content merge' '\n \tcat >expect <<-\\EOF &&\n \tfile\n \tfile2\n+\tfile3\n \tEOF\n \tgit read-tree -i -m base ten two &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n-- \n2.31.0\n\n"},{"id":"419567","messageId":"20210317204939.17890-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 07/15] update-index: move add_cacheinfo() to read-cache.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:31Z","receivedAt":"2021-03-17T20:57:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves the function add_cacheinfo() that already exists in\nupdate-index.c to update-index.c, renames it add_to_index_cacheinfo(),\nand adds an `istate' parameter.  The new cache entry is returned through\na pointer passed in the parameters.  The return value is either 0\n(success), -1 (invalid path), or -2 (failed to add the file in the\nindex).\n\nThis will become useful in the next commit, when the three-way merge\nwill need to call this function.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/update-index.c | 25 +++++++------------------\n cache.h                |  8 ++++++++\n read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n 3 files changed, 50 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 79087bccea..6b86e89840 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -404,27 +404,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n \t\t\t const char *path, int stage)\n {\n-\tint len, option;\n-\tstruct cache_entry *ce;\n+\tint res;\n \n-\tif (!verify_path(path, mode))\n-\t\treturn error(\"Invalid path '%s'\", path);\n-\n-\tlen = strlen(path);\n-\tce = make_empty_cache_entry(&the_index, len);\n-\n-\toidcpy(&ce->oid, oid);\n-\tmemcpy(ce->name, path, len);\n-\tce->ce_flags = create_ce_flags(stage);\n-\tce->ce_namelen = len;\n-\tce->ce_mode = create_ce_mode(mode);\n-\tif (assume_unchanged)\n-\t\tce->ce_flags |= CE_VALID;\n-\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n-\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n-\tif (add_cache_entry(ce, option))\n+\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n+\t\t\t\t     allow_add, allow_replace, NULL);\n+\tif (res == ADD_TO_INDEX_CACHEINFO_INVALID_PATH)\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\tif (res == ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD)\n \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n \t\t\t     path);\n+\n \treport(\"add '%s'\", path);\n \treturn 0;\n }\ndiff --git a/cache.h b/cache.h\nindex 6fda8091f1..41e30c0da2 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -832,6 +832,14 @@ int remove_file_from_index(struct index_state *, const char *path);\n int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n int add_file_to_index(struct index_state *, const char *path, int flags);\n \n+#define ADD_TO_INDEX_CACHEINFO_INVALID_PATH (-1)\n+#define ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD (-2)\n+\n+int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **ce_ret);\n+\n int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\ndiff --git a/read-cache.c b/read-cache.c\nindex 1e9a50c6c7..b514523ca1 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1350,6 +1350,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n \treturn 0;\n }\n \n+int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **ce_ret)\n+{\n+\tint len, option;\n+\tstruct cache_entry *ce;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn ADD_TO_INDEX_CACHEINFO_INVALID_PATH;\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(stage);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n+\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n+\n+\tif (add_index_entry(istate, ce, option)) {\n+\t\tdiscard_cache_entry(ce);\n+\t\treturn ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD;\n+\t}\n+\n+\tif (ce_ret)\n+\t\t*ce_ret = ce;\n+\n+\treturn 0;\n+}\n+\n /*\n  * \"refresh\" does not calculate a new sha1 file or bring the\n  * cache up-to-date for mode/content changes. But what it\n-- \n2.31.0\n\n"},{"id":"419560","messageId":"20210317204939.17890-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 06/15] merge-index: add a new way to invoke `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:30Z","receivedAt":"2021-03-17T20:57:41Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' will be rewritten and libified, there may be\ncases where there is no executable named this way (ie. when git is\ncompiled with `SKIP_DASHED_BUILT_INS' enabled).  This adds a new way to\ninvoke this particular program even if it does not exist, by passing\n`--use=merge-one-file' to merge-index.  For now, it still forks.\n\nThe test suite and shell scripts (git-merge-octopus.sh and\ngit-merge-resolve.sh) are updated to use this new convention.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Documentation/git-merge-index.txt |  7 ++++---\n builtin/merge-index.c             | 25 ++++++++++++++++++++++---\n git-merge-octopus.sh              |  2 +-\n git-merge-resolve.sh              |  2 +-\n t/t6060-merge-index.sh            |  8 ++++----\n 5 files changed, 32 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt\nindex 2ab84a91e5..57e7e03b4c 100644\n--- a/Documentation/git-merge-index.txt\n+++ b/Documentation/git-merge-index.txt\n@@ -9,7 +9,7 @@ git-merge-index - Run a merge for files needing merging\n SYNOPSIS\n --------\n [verse]\n-'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*)\n+'git merge-index' [-o] [-q] (<merge-program> | --use=merge-one-file) (-a | [--] <file>*)\n \n DESCRIPTION\n -----------\n@@ -44,8 +44,9 @@ code.\n Typically this is run with a script calling Git's imitation of\n the 'merge' command from the RCS package.\n \n-A sample script called 'git merge-one-file' is included in the\n-distribution.\n+A sample script called 'git merge-one-file' used to be included in the\n+distribution. This program must now be called with\n+'--use=merge-one-file'.\n \n ALERT ALERT ALERT! The Git \"merge object order\" is different from the\n RCS 'merge' program merge object order. In the above ordering, the\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 49ddf3f9cd..fd5b1a5a92 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,4 +1,5 @@\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n \n@@ -37,7 +38,10 @@ static int merge_one_file_spawn(struct index_state *istate,\n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tmerge_fn merge_action = merge_one_file_spawn;\n+\tstruct lock_file lock = LOCK_INIT;\n \tstruct repository *r = the_repository;\n+\tconst char *use_internal = NULL;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -45,7 +49,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tsignal(SIGCHLD, SIG_DFL);\n \n \tif (argc < 3)\n-\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n+\t\tusage(\"git merge-index [-o] [-q] (<merge-program> | --use=merge-one-file) (-a | [--] [<filename>...])\");\n \n \tif (repo_read_index(r) < 0)\n \t\tdie(\"invalid index\");\n@@ -61,6 +65,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t}\n \n \tpgm = argv[i++];\n+\tsetup_work_tree();\n+\n+\tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n+\t\tif (!strcmp(use_internal, \"merge-one-file\"))\n+\t\t\tpgm = \"git-merge-one-file\";\n+\t\telse\n+\t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n+\t}\n \n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n@@ -71,13 +83,20 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all_index(r->index, one_shot, quiet,\n-\t\t\t\t\t\t       merge_one_file_spawn, NULL);\n+\t\t\t\t\t\t       merge_action, NULL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_index_path(r->index, one_shot, quiet, arg,\n-\t\t\t\t\tmerge_one_file_spawn, NULL);\n+\t\t\t\t\tmerge_action, NULL);\n+\t}\n+\n+\tif (is_lock_file_locked(&lock)) {\n+\t\tif (err)\n+\t\t\trollback_lock_file(&lock);\n+\t\telse\n+\t\t\treturn write_locked_index(r->index, &lock, COMMIT_LOCK);\n \t}\n \n \treturn err;\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\nindex 7d19d37951..2770891960 100755\n--- a/git-merge-octopus.sh\n+++ b/git-merge-octopus.sh\n@@ -100,7 +100,7 @@ do\n \tif test $? -ne 0\n \tthen\n \t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n+\t\tgit merge-index -o --use=merge-one-file -a ||\n \t\tOCTOPUS_FAILURE=1\n \t\tnext=$(git write-tree 2>/dev/null)\n \tfi\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\nindex 343fe7bccd..0b4fc88b61 100755\n--- a/git-merge-resolve.sh\n+++ b/git-merge-resolve.sh\n@@ -45,7 +45,7 @@ then\n \texit 0\n else\n \techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n+\tif git merge-index -o --use=merge-one-file -a\n \tthen\n \t\texit 0\n \telse\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 0cbd8a1f7f..d0cdfeddc1 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -50,8 +50,8 @@ test_expect_success 'read-tree does not resolve content merge' '\n \ttest_cmp expect unmerged\n '\n \n-test_expect_success 'git merge-index git-merge-one-file resolves' '\n-\tgit merge-index git-merge-one-file -a &&\n+test_expect_success 'git merge-index --use=merge-one-file resolves' '\n+\tgit merge-index --use=merge-one-file -a &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_must_be_empty unmerged &&\n \ttest_cmp expect-merged file &&\n@@ -83,7 +83,7 @@ test_expect_success 'merge-one-file respects GIT_WORK_TREE' '\n \t export GIT_WORK_TREE &&\n \t GIT_INDEX_FILE=$PWD/merge.index &&\n \t export GIT_INDEX_FILE &&\n-\t git merge-index git-merge-one-file -a &&\n+\t git merge-index --use=merge-one-file -a &&\n \t git cat-file blob :file >work/file-index\n \t) &&\n \ttest_cmp expect-merged bare.git/work/file &&\n@@ -98,7 +98,7 @@ test_expect_success 'merge-one-file respects core.worktree' '\n \t export GIT_DIR &&\n \t git config core.worktree \"$PWD/child\" &&\n \t git read-tree -i -m base ten two &&\n-\t git merge-index git-merge-one-file -a &&\n+\t git merge-index --use=merge-one-file -a &&\n \t git cat-file blob :file >file-index\n \t) &&\n \ttest_cmp expect-merged subdir/child/file &&\n-- \n2.31.0\n\n"},{"id":"419562","messageId":"20210317204939.17890-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 09/15] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:33Z","receivedAt":"2021-03-17T20:57:41Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all_index() function.\n\nThe index is read in cmd_merge_resolve(), and is wrote back by\nmerge_strategies_resolve().\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |  2 +-\n builtin.h               |  1 +\n builtin/merge-resolve.c | 74 ++++++++++++++++++++++++++++++++\n git-merge-resolve.sh    | 54 -----------------------\n git.c                   |  1 +\n merge-strategies.c      | 95 +++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |  5 +++\n 7 files changed, 177 insertions(+), 55 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex e2e4389f76..8fccc38006 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -600,7 +600,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1102,6 +1101,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex 227c133036..c3029cef46 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -181,6 +181,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..0f2e487c4d\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,74 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\tstruct repository *r = the_repository;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--\"))\n+\t\t\tsep_seen = 1;\n+\t\telse if (!strcmp(argv[i], \"-h\"))\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n+\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tcommit_list_insert(commit, &remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Give up if we are given two or more remotes.  Not handling\n+\t * octopus.\n+\t */\n+\tif (remote && remote->next)\n+\t\treturn 2;\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (!bases)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_resolve(r, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex 0b4fc88b61..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,54 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o --use=merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex 95eb74efe1..ce1f237369 100644\n--- a/git.c\n+++ b/git.c\n@@ -548,6 +548,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 2717af51fd..a51700dae5 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,6 +1,9 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n@@ -272,3 +275,95 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \n \treturn err;\n }\n+\n+static int fast_forward(struct repository *r, struct tree_desc *t,\n+\t\t\tint nr, int aggressive)\n+{\n+\tstruct unpack_trees_options opts;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn error(_(\"unable to write new index file\"));\n+\n+\treturn 0;\n+}\n+\n+static int add_tree(struct tree *tree, struct tree_desc *t)\n+{\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct object_id head, oid;\n+\tstruct commit_list *i;\n+\tint nr = 0;\n+\n+\tif (head_arg)\n+\t\tget_oid(head_arg, &head);\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\n+\tfor (i = bases; i && i->item; i = i->next) {\n+\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (head_arg) {\n+\t\tstruct tree *tree = parse_tree_indirect(&head);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n+\t\treturn 2;\n+\n+\tif (fast_forward(r, t, nr, 1))\n+\t\treturn 2;\n+\n+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n+\t\tint ret;\n+\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n+\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 8705a550ca..bba4bf999c 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_three_way(struct index_state *istate,\n@@ -28,4 +29,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.31.0\n\n"},{"id":"419563","messageId":"20210317204939.17890-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 10/15] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:34Z","receivedAt":"2021-03-17T20:57:41Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"better_branch_name() will be used by merge-octopus once it is rewritten\nin C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex a4bfd8fc51..972243b5e9 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex 41e30c0da2..e89a8c3404 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1852,7 +1852,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 5fb88af102..801d673c5f 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -109,3 +109,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.31.0\n\n"},{"id":"419564","messageId":"20210317204939.17890-13-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 12/15] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:36Z","receivedAt":"2021-03-17T20:57:41Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex eb00b273e6..87921497a2 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -43,6 +43,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -755,6 +756,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n+\t} else if (!strcmp(strategy, \"resolve\")) {\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.31.0\n\n"},{"id":"419566","messageId":"20210317204939.17890-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 11/15] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:35Z","receivedAt":"2021-03-17T20:57:41Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to\n   write_index_as_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all_index().\n\nThe index is read in cmd_merge_octopus(), and is wrote back by\nmerge_strategies_octopus().\n\nHere to, merge_strategies_octopus() takes two commit lists and a string\nto reduce frictions when try_merge_strategies() will be modified to call\nit directly.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  70 ++++++++++++++++\n git-merge-octopus.sh    | 112 -------------------------\n git.c                   |   1 +\n merge-strategies.c      | 175 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 251 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 8fccc38006..fa8f1a2ddf 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -599,7 +599,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1098,6 +1097,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex c3029cef46..ef2b65c9d0 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -177,6 +177,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..9b9939b6b2\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,70 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\tstruct repository *r = the_repository;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n+\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Reject if this is not an octopus -- resolve should be used\n+\t * instead.\n+\t */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\treturn merge_strategies_octopus(r, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 2770891960..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o --use=merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex ce1f237369..c47cc441a3 100644\n--- a/git.c\n+++ b/git.c\n@@ -543,6 +543,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex a51700dae5..ebc0d0b1e2 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"lockfile.h\"\n #include \"merge-strategies.h\"\n@@ -367,3 +368,177 @@ int merge_strategies_resolve(struct repository *r,\n \n \treturn 0;\n }\n+\n+static int write_tree(struct repository *r, struct tree **reference_tree)\n+{\n+\tstruct object_id oid;\n+\tint ret;\n+\n+\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file,\n+\t\t\t\t\tWRITE_TREE_SILENT, NULL)))\n+\t\t*reference_tree = lookup_tree(r, &oid);\n+\n+\treturn ret;\n+}\n+\n+static int octopus_fast_forward(struct repository *r, const char *branch_name,\n+\t\t\t\tstruct tree *tree_head, struct tree *current_tree,\n+\t\t\t\tstruct tree **reference_tree)\n+{\n+\t/*\n+\t * The first head being merged was a fast-forward.  Advance the\n+\t * reference commit to the head being merged, and use that tree\n+\t * as the intermediate result of the merge.  We still need to\n+\t * count this as part of the parent set.\n+\t */\n+\tstruct tree_desc t[2];\n+\n+\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n+\tif (add_tree(current_tree, t + 1))\n+\t\treturn -1;\n+\tif (fast_forward(r, t, 2, 0))\n+\t\treturn -1;\n+\tif (write_tree(r, reference_tree))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int octopus_do_merge(struct repository *r, const char *branch_name,\n+\t\t\t    struct commit_list *common, struct tree *current_tree,\n+\t\t\t    struct tree **reference_tree)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct commit_list *i;\n+\tint nr = 0, ret = 0;\n+\n+\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\tfor (i = common; i; i = i->next) {\n+\t\tstruct tree *tree = repo_get_commit_tree(r, i->item);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn -1;\n+\t}\n+\n+\tif (add_tree(*reference_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (add_tree(current_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (fast_forward(r, t, nr, 1))\n+\t\treturn 2;\n+\n+\tif (write_tree(r, reference_tree)) {\n+\t\tstruct lock_file lock = LOCK_INIT;\n+\n+\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, NULL);\n+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\t\twrite_tree(r, reference_tree);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint ff_merge = 1, ret = 0, nr_references = 1;\n+\tstruct commit **reference_commits, *head_commit;\n+\tstruct tree *reference_tree, *head_tree;\n+\tstruct commit_list *i;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\thead_commit = lookup_commit_reference(r, &head);\n+\thead_tree = repo_get_commit_tree(r, head_commit);\n+\n+\tif (parse_tree(head_tree))\n+\t\treturn 2;\n+\n+\tif (repo_index_has_changes(r, head_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn 2;\n+\t}\n+\n+\tCALLOC_ARRAY(reference_commits, commit_list_count(remotes) + 1);\n+\treference_commits[0] = head_commit;\n+\treference_tree = head_tree;\n+\n+\tfor (i = remotes; i && i->item; i = i->next) {\n+\t\tstruct commit *c = i->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n+\t\tstruct commit_list *common, *j;\n+\t\tchar *branch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tint up_to_date = 0;\n+\n+\t\tcommon = repo_get_merge_bases_many(r, c, nr_references, reference_commits);\n+\t\tif (!common) {\n+\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tfree(reference_commits);\n+\n+\t\t\treturn 2;\n+\t\t}\n+\n+\t\tfor (j = common; j && !up_to_date && ff_merge; j = j->next) {\n+\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n+\n+\t\t\tif (!j->next &&\n+\t\t\t    !oideq(&j->item->object.oid,\n+\t\t\t\t   &reference_commits[nr_references - 1]->object.oid))\n+\t\t\t\tff_merge = 0;\n+\t\t}\n+\n+\t\tif (up_to_date) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (ff_merge) {\n+\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n+\t\t\t\t\t\t   current_tree, &reference_tree);\n+\t\t\tnr_references = 0;\n+\t\t} else {\n+\t\t\tret = octopus_do_merge(r, branch_name, common,\n+\t\t\t\t\t       current_tree, &reference_tree);\n+\t\t}\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\n+\t\tif (ret == -1 || ret == 2)\n+\t\t\tbreak;\n+\t\telse if (ret && i->next) {\n+\t\t\t/*\n+\t\t\t * We allow only last one to have a\n+\t\t\t * hand-resolvable conflicts.  Last round failed\n+\t\t\t * and we still had a head to merge.\n+\t\t\t */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tfree(reference_commits);\n+\t\t\treturn 2;\n+\t\t}\n+\n+\t\treference_commits[nr_references++] = c;\n+\t}\n+\n+\tfree(reference_commits);\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex bba4bf999c..8de2249ee6 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -32,5 +32,8 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.31.0\n\n"},{"id":"419569","messageId":"20210317204939.17890-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 08/15] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:32Z","receivedAt":"2021-03-17T20:57:41Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_to_index_cacheinfo();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_index();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and xdl_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are removed, as this is needed\n   to have the correct permission bits on the merged file from the\n   script, but not in the C version;\n\n - calls to `update-index' are replaced by calls to add_file_to_index().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nThis also teaches `merge-index' to call merge_three_way() (when invoked\nwith `--use=merge-one-file') without forking using a new callback,\nmerge_one_file_func().\n\nTo avoid any issue with a shrinking index because of the merge function\nused (directly in the process or by forking), as described earlier, the\niterator of the loop of merge_all_index() is increased by the number of\nentries with the same name, minus the difference between the number of\nentries in the index before and after the merge.\n\nThis should handle a shrinking index correctly, but could lead to issues\nwith a growing index.  However, this case is not treated, as there is no\ncallback that can produce such a case.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   2 +-\n builtin.h                       |   1 +\n builtin/merge-index.c           |   9 +-\n builtin/merge-one-file.c        |  94 +++++++++++++++\n git-merge-one-file.sh           | 167 --------------------------\n git.c                           |   1 +\n merge-strategies.c              | 207 +++++++++++++++++++++++++++++++-\n merge-strategies.h              |  13 ++\n t/t6060-merge-index.sh          |   2 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 10 files changed, 321 insertions(+), 177 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n\ndiff --git a/Makefile b/Makefile\nindex 1b1dc49e86..e2e4389f76 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -600,7 +600,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -1100,6 +1099,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex b6ce981b73..227c133036 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -179,6 +179,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex fd5b1a5a92..04d38aa130 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -38,7 +38,7 @@ static int merge_one_file_spawn(struct index_state *istate,\n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n-\tmerge_fn merge_action = merge_one_file_spawn;\n+\tmerge_fn merge_action;\n \tstruct lock_file lock = LOCK_INIT;\n \tstruct repository *r = the_repository;\n \tconst char *use_internal = NULL;\n@@ -69,10 +69,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \n \tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n \t\tif (!strcmp(use_internal, \"merge-one-file\"))\n-\t\t\tpgm = \"git-merge-one-file\";\n+\t\t\tmerge_action = merge_one_file_func;\n \t\telse\n \t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n-\t}\n+\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\t} else\n+\t\tmerge_action = merge_one_file_spawn;\n \n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..ad99c6dbd4\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,94 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file object name (or empty)\n+ *   argv[2] - file in branch1 object name (or empty)\n+ *   argv[3] - file in branch2 object name (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+static int read_mode(const char *name, const char *arg, unsigned int *mode)\n+{\n+\tchar *last;\n+\tint ret = 0;\n+\n+\t*mode = strtol(arg, &last, 8);\n+\n+\tif (*last)\n+\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n+\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n+\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n+\n+\treturn ret;\n+}\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct repository *r = the_repository;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (!get_oid_hex(argv[1], &orig_blob)) {\n+\t\tp_orig_blob = &orig_blob;\n+\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n+\t} else if (!*argv[1] && *argv[5])\n+\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[2], &our_blob)) {\n+\t\tp_our_blob = &our_blob;\n+\t\tret = read_mode(\"our\", argv[6], &our_mode);\n+\t} else if (!*argv[2] && *argv[6])\n+\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n+\n+\tif (!get_oid_hex(argv[3], &their_blob)) {\n+\t\tp_their_blob = &their_blob;\n+\t\tret = read_mode(\"their\", argv[7], &their_mode);\n+\t} else if (!*argv[3] && *argv[7])\n+\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_three_way(r->index, p_orig_blob, p_our_blob, p_their_blob,\n+\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn write_locked_index(r->index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex 9bc077a025..95eb74efe1 100644\n--- a/git.c\n+++ b/git.c\n@@ -544,6 +544,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex c80f964612..2717af51fd 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,197 @@\n #include \"cache.h\"\n+#include \"dir.h\"\n #include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n+\t\t\t\t     const struct object_id *oid, const char *path,\n+\t\t\t\t     int checkout)\n+{\n+\tstruct cache_entry *ce;\n+\tint res;\n+\n+\tres = add_to_index_cacheinfo(istate, mode, oid, path, 0, 1, 1, &ce);\n+\tif (res == -1)\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\telse if (res == -2)\n+\t\treturn -1;\n+\n+\tif (checkout) {\n+\t\tstruct checkout state = CHECKOUT_INIT;\n+\n+\t\tstate.istate = istate;\n+\t\tstate.force = 1;\n+\t\tstate.base_dir = \"\";\n+\t\tstate.base_dir_len = 0;\n+\n+\t\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((!our_blob && orig_mode != their_mode) ||\n+\t    (!their_blob && orig_mode != our_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tssize_t written;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, &null_oid);\n+\t}\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n+\n+\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"Failed to execute internal merge\"));\n+\t}\n+\n+\tif (ret > 0 || !orig_blob)\n+\t\tret = error(_(\"content conflict in %s\"), path);\n+\tif (our_mode != their_mode)\n+\t\tret = error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t    orig_mode, our_mode, their_mode, path);\n+\n+\tunlink(path);\n+\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n+\t}\n+\n+\twritten = write_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (written < 0)\n+\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_three_way(struct index_state *istate,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!our_blob && !their_blob) ||\n+\t     (!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n+\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n+\t\treturn merge_one_file_deleted(istate, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\t} else if (!orig_blob && our_blob && !their_blob) {\n+\t\t/*\n+\t\t * Added in ours.  The other side did not add and we\n+\t\t * added so there is nothing to be done, except making\n+\t\t * the path merged.\n+\t\t */\n+\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 0);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\treturn add_merge_result_to_index(istate, their_mode, their_blob, path, 1);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\t/* Added in both, identically (check for same permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 1);\n+\t} else if (our_blob && their_blob) {\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(istate,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\t} else {\n+\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n+\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n+\n+\t\tif (orig_blob)\n+\t\t\toid_to_hex_r(orig_hex, orig_blob);\n+\t\tif (our_blob)\n+\t\t\toid_to_hex_r(our_hex, our_blob);\n+\t\tif (their_blob)\n+\t\t\toid_to_hex_r(their_hex, their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_one_file_func(struct index_state *istate,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data)\n+{\n+\treturn merge_three_way(istate,\n+\t\t\t       orig_blob, our_blob, their_blob, path,\n+\t\t\t       orig_mode, our_mode, their_mode);\n+}\n \n static int merge_entry(struct index_state *istate, int quiet, unsigned int pos,\n \t\t       const char *path, int *err, merge_fn fn, void *data)\n@@ -54,17 +246,24 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data)\n {\n \tint err = 0, ret;\n-\tunsigned int i;\n+\tunsigned int i, prev_nr;\n \n \tfor (i = 0; i < istate->cache_nr; i++) {\n \t\tconst struct cache_entry *ce = istate->cache[i];\n \t\tif (!ce_stage(ce))\n \t\t\tcontinue;\n \n+\t\tprev_nr = istate->cache_nr;\n \t\tret = merge_entry(istate, quiet || oneshot, i, ce->name, &err, fn, data);\n-\t\tif (ret > 0)\n-\t\t\ti += ret - 1;\n-\t\telse if (ret == -1)\n+\t\tif (ret > 0) {\n+\t\t\t/*\n+\t\t\t * Don't bother handling an index that has\n+\t\t\t * grown, since merge_one_file_func() can't grow\n+\t\t\t * it, and merge_one_file_spawn() can't change\n+\t\t\t * it.\n+\t\t\t */\n+\t\t\ti += ret - (prev_nr - istate->cache_nr) - 1;\n+\t\t} else if (ret == -1)\n \t\t\treturn -1;\n \n \t\tif (err && !oneshot)\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 88f476f170..8705a550ca 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -3,6 +3,12 @@\n \n #include \"object.h\"\n \n+int merge_three_way(struct index_state *istate,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n+\n typedef int (*merge_fn)(struct index_state *istate,\n \t\t\tconst struct object_id *orig_blob,\n \t\t\tconst struct object_id *our_blob,\n@@ -10,6 +16,13 @@ typedef int (*merge_fn)(struct index_state *istate,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_func(struct index_state *istate,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n \t\t     const char *path, merge_fn fn, void *data);\n int merge_all_index(struct index_state *istate, int oneshot, int quiet,\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex d0cdfeddc1..d9c07965dc 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -72,7 +72,7 @@ test_expect_success 'merge-one-file fails without a work tree' '\n \t(cd bare.git &&\n \t GIT_INDEX_FILE=$PWD/merge.index &&\n \t export GIT_INDEX_FILE &&\n-\t test_must_fail git merge-index git-merge-one-file -a\n+\t test_must_fail git merge-index --use=merge-one-file -a\n \t)\n '\n \ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2ce104aca7..075da1f55f 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -97,7 +97,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.31.0\n\n"},{"id":"419561","messageId":"20210317204939.17890-16-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 15/15] sequencer: use the \"octopus\" merge strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:39Z","receivedAt":"2021-03-17T20:57:42Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ec8e9bda22..683ebfc8e2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2054,6 +2054,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else {\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.31.0\n\n"},{"id":"419568","messageId":"20210317204939.17890-15-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 14/15] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:38Z","receivedAt":"2021-03-17T20:57:42Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 14 +++++++++++---\n 1 file changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex d2332d3e17..ec8e9bda22 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -34,6 +34,7 @@\n #include \"commit-reach.h\"\n #include \"rebase-interactive.h\"\n #include \"reset.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2049,9 +2050,16 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else {\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\t\t}\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.31.0\n\n"},{"id":"419570","messageId":"20210317204939.17890-14-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v7 13/15] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-17T20:49:37Z","receivedAt":"2021-03-17T20:57:42Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 87921497a2..79f1e8bdd1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -759,6 +759,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\")) {\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\t} else if (!strcmp(strategy, \"octopus\")) {\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.31.0\n\n"},{"id":"419976","messageId":"nycvar.QRO.7.76.6.2103222235150.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"20210317204939.17890-4-alban.gruin@gmail.com","subject":"Re: [PATCH v7 03/15] t6060: add tests for removed files","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-22T21:36:46Z","receivedAt":"2021-03-22T21:37:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Wed, 17 Mar 2021, Alban Gruin wrote:\n\n> Until now, t6060 did not not check git-mere-one-file's behaviour when a\n\nChanneling my inner Eric Sunshine: s/mere-one/merge-one/ ;-)\n\n> file is deleted in a branch.  To avoid regressions on this during the\n> conversion, this adds a new file, `file3', in the commit tagged as`base', and\n\nMaybe \"during the conversion from shell script to C\"?\n\nOther than that, looks good to me! Thanks,\nDscho\n\n> deletes it in the commit tagged as `two'.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  t/t6060-merge-index.sh | 5 ++++-\n>  1 file changed, 4 insertions(+), 1 deletion(-)\n>\n> diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\n> index 9e15ceb957..0cbd8a1f7f 100755\n> --- a/t/t6060-merge-index.sh\n> +++ b/t/t6060-merge-index.sh\n> @@ -8,12 +8,14 @@ test_expect_success 'setup diverging branches' '\n>  \t\techo $i\n>  \tdone >file &&\n>  \tcp file file2 &&\n> -\tgit add file file2 &&\n> +\tcp file file3 &&\n> +\tgit add file file2 file3 &&\n>  \tgit commit -m base &&\n>  \tgit tag base &&\n>  \tsed s/2/two/ <file >tmp &&\n>  \tmv tmp file &&\n>  \tcp file file2 &&\n> +\tgit rm file3 &&\n>  \tgit commit -a -m two &&\n>  \tgit tag two &&\n>  \tgit checkout -b other HEAD^ &&\n> @@ -41,6 +43,7 @@ test_expect_success 'read-tree does not resolve content merge' '\n>  \tcat >expect <<-\\EOF &&\n>  \tfile\n>  \tfile2\n> +\tfile3\n>  \tEOF\n>  \tgit read-tree -i -m base ten two &&\n>  \tgit diff-files --name-only --diff-filter=U >unmerged &&\n> --\n> 2.31.0\n>\n>\n"},{"id":"419978","messageId":"nycvar.QRO.7.76.6.2103222255550.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"20210317204939.17890-8-alban.gruin@gmail.com","subject":"Re: [PATCH v7 07/15] update-index: move add_cacheinfo() to read-cache.c","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-22T21:59:20Z","receivedAt":"2021-03-22T22:00:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Wed, 17 Mar 2021, Alban Gruin wrote:\n\n> This moves the function add_cacheinfo() that already exists in\n> update-index.c to update-index.c, renames it add_to_index_cacheinfo(),\n> and adds an `istate' parameter.  The new cache entry is returned through\n> a pointer passed in the parameters.  The return value is either 0\n> (success), -1 (invalid path), or -2 (failed to add the file in the\n> index).\n\nThis paragraph still talks about magic numbers, but the code has constants\nfor them. Maybe elevate the commit message to a more generic description\nthat does not spend time on specifying the exact values, but rather lists\nthe three outcomes in plain English?\n\nOther than that, this looks fine to me! Thanks,\nDscho\n\n>\n> This will become useful in the next commit, when the three-way merge\n> will need to call this function.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  builtin/update-index.c | 25 +++++++------------------\n>  cache.h                |  8 ++++++++\n>  read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n>  3 files changed, 50 insertions(+), 18 deletions(-)\n>\n> diff --git a/builtin/update-index.c b/builtin/update-index.c\n> index 79087bccea..6b86e89840 100644\n> --- a/builtin/update-index.c\n> +++ b/builtin/update-index.c\n> @@ -404,27 +404,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n>  static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n>  \t\t\t const char *path, int stage)\n>  {\n> -\tint len, option;\n> -\tstruct cache_entry *ce;\n> +\tint res;\n>\n> -\tif (!verify_path(path, mode))\n> -\t\treturn error(\"Invalid path '%s'\", path);\n> -\n> -\tlen = strlen(path);\n> -\tce = make_empty_cache_entry(&the_index, len);\n> -\n> -\toidcpy(&ce->oid, oid);\n> -\tmemcpy(ce->name, path, len);\n> -\tce->ce_flags = create_ce_flags(stage);\n> -\tce->ce_namelen = len;\n> -\tce->ce_mode = create_ce_mode(mode);\n> -\tif (assume_unchanged)\n> -\t\tce->ce_flags |= CE_VALID;\n> -\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n> -\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n> -\tif (add_cache_entry(ce, option))\n> +\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n> +\t\t\t\t     allow_add, allow_replace, NULL);\n> +\tif (res == ADD_TO_INDEX_CACHEINFO_INVALID_PATH)\n> +\t\treturn error(_(\"Invalid path '%s'\"), path);\n> +\tif (res == ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD)\n>  \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n>  \t\t\t     path);\n> +\n>  \treport(\"add '%s'\", path);\n>  \treturn 0;\n>  }\n> diff --git a/cache.h b/cache.h\n> index 6fda8091f1..41e30c0da2 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -832,6 +832,14 @@ int remove_file_from_index(struct index_state *, const char *path);\n>  int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n>  int add_file_to_index(struct index_state *, const char *path, int flags);\n>\n> +#define ADD_TO_INDEX_CACHEINFO_INVALID_PATH (-1)\n> +#define ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD (-2)\n> +\n> +int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n> +\t\t\t   const struct object_id *oid, const char *path,\n> +\t\t\t   int stage, int allow_add, int allow_replace,\n> +\t\t\t   struct cache_entry **ce_ret);\n> +\n>  int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n>  int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n>  void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\n> diff --git a/read-cache.c b/read-cache.c\n> index 1e9a50c6c7..b514523ca1 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -1350,6 +1350,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n>  \treturn 0;\n>  }\n>\n> +int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n> +\t\t\t   const struct object_id *oid, const char *path,\n> +\t\t\t   int stage, int allow_add, int allow_replace,\n> +\t\t\t   struct cache_entry **ce_ret)\n> +{\n> +\tint len, option;\n> +\tstruct cache_entry *ce;\n> +\n> +\tif (!verify_path(path, mode))\n> +\t\treturn ADD_TO_INDEX_CACHEINFO_INVALID_PATH;\n> +\n> +\tlen = strlen(path);\n> +\tce = make_empty_cache_entry(istate, len);\n> +\n> +\toidcpy(&ce->oid, oid);\n> +\tmemcpy(ce->name, path, len);\n> +\tce->ce_flags = create_ce_flags(stage);\n> +\tce->ce_namelen = len;\n> +\tce->ce_mode = create_ce_mode(mode);\n> +\tif (assume_unchanged)\n> +\t\tce->ce_flags |= CE_VALID;\n> +\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n> +\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n> +\n> +\tif (add_index_entry(istate, ce, option)) {\n> +\t\tdiscard_cache_entry(ce);\n> +\t\treturn ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD;\n> +\t}\n> +\n> +\tif (ce_ret)\n> +\t\t*ce_ret = ce;\n> +\n> +\treturn 0;\n> +}\n> +\n>  /*\n>   * \"refresh\" does not calculate a new sha1 file or bring the\n>   * cache up-to-date for mode/content changes. But what it\n> --\n> 2.31.0\n>\n>\n"},{"id":"419979","messageId":"nycvar.QRO.7.76.6.2103222303210.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"20210317204939.17890-9-alban.gruin@gmail.com","subject":"Re: [PATCH v7 08/15] merge-one-file: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-22T22:20:33Z","receivedAt":"2021-03-22T22:21:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Wed, 17 Mar 2021, Alban Gruin wrote:\n\n> This rewrites `git merge-one-file' from shell to C.  This port is not\n> completely straightforward: to save precious cycles by avoiding reading\n> and flushing the index repeatedly, write temporary files when an\n> operation can be performed in-memory, or allow other function to use the\n> rewrite without forking nor worrying about the index, the calls to\n> external processes are replaced by calls to functions in libgit.a:\n>\n>  - calls to `update-index --add --cacheinfo' are replaced by calls to\n>    add_to_index_cacheinfo();\n>\n>  - calls to `update-index --remove' are replaced by calls to\n>    remove_file_from_index();\n>\n>  - calls to `checkout-index -u -f' are replaced by calls to\n>    checkout_entry();\n>\n>  - calls to `unpack-file' and `merge-files' are replaced by calls to\n>    read_mmblob() and xdl_merge(), respectively, to merge files\n>    in-memory;\n>\n>  - calls to `checkout-index -f --stage=2' are removed, as this is needed\n>    to have the correct permission bits on the merged file from the\n>    script, but not in the C version;\n>\n>  - calls to `update-index' are replaced by calls to add_file_to_index().\n>\n> The bulk of the rewrite is done in a new file in libgit.a,\n> merge-strategies.c.  This will enable the resolve and octopus strategies\n> to directly call it instead of forking.\n>\n> This also fixes a bug present in the original script: instead of\n> checking if a _regular_ file exists when a file exists in the branch to\n> merge, but not in our branch, the rewritten version checks if a file of\n> any kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\n> where the branch to merge had a new file, `a/b', but our branch had a\n> directory there; it should have failed because a directory exists, but\n> it did not because there was no regular file called `a/b'.  This test is\n> now marked as successful.\n>\n> This also teaches `merge-index' to call merge_three_way() (when invoked\n> with `--use=merge-one-file') without forking using a new callback,\n> merge_one_file_func().\n>\n> To avoid any issue with a shrinking index because of the merge function\n> used (directly in the process or by forking), as described earlier, the\n> iterator of the loop of merge_all_index() is increased by the number of\n> entries with the same name, minus the difference between the number of\n> entries in the index before and after the merge.\n>\n> This should handle a shrinking index correctly, but could lead to issues\n> with a growing index.  However, this case is not treated, as there is no\n> callback that can produce such a case.\n\nNice!\n\n> diff --git a/builtin/merge-index.c b/builtin/merge-index.c\n> index fd5b1a5a92..04d38aa130 100644\n> --- a/builtin/merge-index.c\n> +++ b/builtin/merge-index.c\n> @@ -38,7 +38,7 @@ static int merge_one_file_spawn(struct index_state *istate,\n>  int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n> -\tmerge_fn merge_action = merge_one_file_spawn;\n> +\tmerge_fn merge_action;\n>  \tstruct lock_file lock = LOCK_INIT;\n>  \tstruct repository *r = the_repository;\n>  \tconst char *use_internal = NULL;\n> @@ -69,10 +69,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>\n>  \tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n>  \t\tif (!strcmp(use_internal, \"merge-one-file\"))\n> -\t\t\tpgm = \"git-merge-one-file\";\n> +\t\t\tmerge_action = merge_one_file_func;\n>  \t\telse\n>  \t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n> -\t}\n> +\n> +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n> +\t} else\n> +\t\tmerge_action = merge_one_file_spawn;\n\nI would have a slight preference to keep the default initializer, because\nthat makes it easer to reason about. But if you _want_ to keep this patch\nas-is, I won't object.\n\nIt is a bit sad that the conversion cannot be done more incrementally, as\nthere is a lot to unpack in the many different cases that are handled. It\nlooks correct, though.\n\nJust one thing:\n\n>\n>  \tfor (; i < argc; i++) {\n>  \t\tconst char *arg = argv[i];\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..ad99c6dbd4\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,94 @@\n> +/*\n> + * Builtin \"git merge-one-file\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> + *\n> + * This is the git per-file merge utility, called with\n> + *\n> + *   argv[1] - original file object name (or empty)\n> + *   argv[2] - file in branch1 object name (or empty)\n> + *   argv[3] - file in branch2 object name (or empty)\n> + *   argv[4] - pathname in repository\n> + *   argv[5] - original file mode (or empty)\n> + *   argv[6] - file in branch1 mode (or empty)\n> + *   argv[7] - file in branch2 mode (or empty)\n> + *\n> + * Handle some trivial cases. The _really_ trivial cases have been\n> + * handled already by git read-tree, but that one doesn't do any merges\n> + * that might change the tree layout.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"lockfile.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_one_file_usage[] =\n> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n> +\t\"Blob ids and modes should be empty for missing files.\";\n> +\n> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n> +{\n> +\tchar *last;\n> +\tint ret = 0;\n> +\n> +\t*mode = strtol(arg, &last, 8);\n> +\n> +\tif (*last)\n> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n> +\n> +\treturn ret;\n> +}\n> +\n> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> +{\n> +\tstruct object_id orig_blob, our_blob, their_blob,\n> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tstruct repository *r = the_repository;\n> +\n> +\tif (argc != 8)\n> +\t\tusage(builtin_merge_one_file_usage);\n> +\n> +\tif (repo_read_index(r) < 0)\n> +\t\tdie(\"invalid index\");\n> +\n> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n> +\n> +\tif (!get_oid_hex(argv[1], &orig_blob)) {\n> +\t\tp_orig_blob = &orig_blob;\n> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n> +\t} else if (!*argv[1] && *argv[5])\n> +\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n\nHere, it looks as if the case of an empty `argv[1]` is not handled\n_explicitly_, but we rely on `get_oid_hex()` to return non-zero, and then\nwe rely on the second arm _also_ not re-assigning `orig_blob`.\n\nI wonder whether this could be checked, and whether it would make sense to\nfold this, along with most of these 5 lines, into the `read_mode()` helper\nfunction (DRYing up the code even further).\n\nAs for the rest of the patch, it is totally possible that I missed a bug,\nbut it looks correct to me, and the added regression tests give me a good\nfeeling about the patch, too.\n\nThanks,\nDscho\n\n> +\n> +\tif (!get_oid_hex(argv[2], &our_blob)) {\n> +\t\tp_our_blob = &our_blob;\n> +\t\tret = read_mode(\"our\", argv[6], &our_mode);\n> +\t} else if (!*argv[2] && *argv[6])\n> +\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n> +\n> +\tif (!get_oid_hex(argv[3], &their_blob)) {\n> +\t\tp_their_blob = &their_blob;\n> +\t\tret = read_mode(\"their\", argv[7], &their_mode);\n> +\t} else if (!*argv[3] && *argv[7])\n> +\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n> +\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tret = merge_three_way(r->index, p_orig_blob, p_our_blob, p_their_blob,\n> +\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n> +\n> +\tif (ret) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn !!ret;\n> +\t}\n> +\n> +\treturn write_locked_index(r->index, &lock, COMMIT_LOCK);\n> +}\n> diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\n> deleted file mode 100755\n> index f6d9852d2f..0000000000\n> --- a/git-merge-one-file.sh\n> +++ /dev/null\n> @@ -1,167 +0,0 @@\n> -#!/bin/sh\n> -#\n> -# Copyright (c) Linus Torvalds, 2005\n> -#\n> -# This is the git per-file merge script, called with\n> -#\n> -#   $1 - original file SHA1 (or empty)\n> -#   $2 - file in branch1 SHA1 (or empty)\n> -#   $3 - file in branch2 SHA1 (or empty)\n> -#   $4 - pathname in repository\n> -#   $5 - original file mode (or empty)\n> -#   $6 - file in branch1 mode (or empty)\n> -#   $7 - file in branch2 mode (or empty)\n> -#\n> -# Handle some trivial cases.. The _really_ trivial cases have\n> -# been handled already by git read-tree, but that one doesn't\n> -# do any merges that might change the tree layout.\n> -\n> -USAGE='<orig blob> <our blob> <their blob> <path>'\n> -USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n> -LONG_USAGE=\"usage: git merge-one-file $USAGE\n> -\n> -Blob ids and modes should be empty for missing files.\"\n> -\n> -SUBDIRECTORY_OK=Yes\n> -. git-sh-setup\n> -cd_to_toplevel\n> -require_work_tree\n> -\n> -if test $# != 7\n> -then\n> -\techo \"$LONG_USAGE\"\n> -\texit 1\n> -fi\n> -\n> -case \"${1:-.}${2:-.}${3:-.}\" in\n> -#\n> -# Deleted in both or deleted in one and unchanged in the other\n> -#\n> -\"$1..\" | \"$1.$1\" | \"$1$1.\")\n> -\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n> -\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n> -\tthen\n> -\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n> -\t\techo \"ERROR: permissions changed on the other.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\n> -\tif test -n \"$2\"\n> -\tthen\n> -\t\techo \"Removing $4\"\n> -\telse\n> -\t\t# read-tree checked that index matches HEAD already,\n> -\t\t# so we know we do not have this path tracked.\n> -\t\t# there may be an unrelated working tree file here,\n> -\t\t# which we should just leave unmolested.  Make sure\n> -\t\t# we do not have it in the index, though.\n> -\t\texec git update-index --remove -- \"$4\"\n> -\tfi\n> -\tif test -f \"$4\"\n> -\tthen\n> -\t\trm -f -- \"$4\" &&\n> -\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n> -\tfi &&\n> -\t\texec git update-index --remove -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Added in one.\n> -#\n> -\".$2.\")\n> -\t# the other side did not add and we added so there is nothing\n> -\t# to be done, except making the path merged.\n> -\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n> -\t;;\n> -\"..$3\")\n> -\techo \"Adding $4\"\n> -\tif test -f \"$4\"\n> -\tthen\n> -\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n> -\t\texec git checkout-index -u -f -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Added in both, identically (check for same permissions).\n> -#\n> -\".$3$2\")\n> -\tif test \"$6\" != \"$7\"\n> -\tthen\n> -\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n> -\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n> -\t\texit 1\n> -\tfi\n> -\techo \"Adding $4\"\n> -\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n> -\t\texec git checkout-index -u -f -- \"$4\"\n> -\t;;\n> -\n> -#\n> -# Modified in both, but differently.\n> -#\n> -\"$1$2$3\" | \".$2$3\")\n> -\n> -\tcase \",$6,$7,\" in\n> -\t*,120000,*)\n> -\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n> -\t\texit 1\n> -\t\t;;\n> -\t*,160000,*)\n> -\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n> -\t\texit 1\n> -\t\t;;\n> -\tesac\n> -\n> -\tsrc1=$(git unpack-file $2)\n> -\tsrc2=$(git unpack-file $3)\n> -\tcase \"$1\" in\n> -\t'')\n> -\t\techo \"Added $4 in both, but differently.\"\n> -\t\torig=$(git unpack-file $(git hash-object /dev/null))\n> -\t\t;;\n> -\t*)\n> -\t\techo \"Auto-merging $4\"\n> -\t\torig=$(git unpack-file $1)\n> -\t\t;;\n> -\tesac\n> -\n> -\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n> -\tret=$?\n> -\tmsg=\n> -\tif test $ret != 0 || test -z \"$1\"\n> -\tthen\n> -\t\tmsg='content conflict'\n> -\t\tret=1\n> -\tfi\n> -\n> -\t# Create the working tree file, using \"our tree\" version from the\n> -\t# index, and then store the result of the merge.\n> -\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n> -\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n> -\n> -\tif test \"$6\" != \"$7\"\n> -\tthen\n> -\t\tif test -n \"$msg\"\n> -\t\tthen\n> -\t\t\tmsg=\"$msg, \"\n> -\t\tfi\n> -\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n> -\t\tret=1\n> -\tfi\n> -\n> -\tif test $ret != 0\n> -\tthen\n> -\t\techo \"ERROR: $msg in $4\" >&2\n> -\t\texit 1\n> -\tfi\n> -\texec git update-index -- \"$4\"\n> -\t;;\n> -\n> -*)\n> -\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n> -\t;;\n> -esac\n> -exit 1\n> diff --git a/git.c b/git.c\n> index 9bc077a025..95eb74efe1 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -544,6 +544,7 @@ static struct cmd_struct commands[] = {\n>  \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n>  \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n>  \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n> +\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n>  \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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> diff --git a/merge-strategies.c b/merge-strategies.c\n> index c80f964612..2717af51fd 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -1,5 +1,197 @@\n>  #include \"cache.h\"\n> +#include \"dir.h\"\n>  #include \"merge-strategies.h\"\n> +#include \"xdiff-interface.h\"\n> +\n> +static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n> +\t\t\t\t     const struct object_id *oid, const char *path,\n> +\t\t\t\t     int checkout)\n> +{\n> +\tstruct cache_entry *ce;\n> +\tint res;\n> +\n> +\tres = add_to_index_cacheinfo(istate, mode, oid, path, 0, 1, 1, &ce);\n> +\tif (res == -1)\n> +\t\treturn error(_(\"Invalid path '%s'\"), path);\n> +\telse if (res == -2)\n> +\t\treturn -1;\n> +\n> +\tif (checkout) {\n> +\t\tstruct checkout state = CHECKOUT_INIT;\n> +\n> +\t\tstate.istate = istate;\n> +\t\tstate.force = 1;\n> +\t\tstate.base_dir = \"\";\n> +\t\tstate.base_dir_len = 0;\n> +\n> +\t\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n> +\t\t\treturn error(_(\"%s: cannot checkout file\"), path);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int merge_one_file_deleted(struct index_state *istate,\n> +\t\t\t\t  const struct object_id *our_blob,\n> +\t\t\t\t  const struct object_id *their_blob, const char *path,\n> +\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif ((!our_blob && orig_mode != their_mode) ||\n> +\t    (!their_blob && orig_mode != our_mode))\n> +\t\treturn error(_(\"File %s deleted on one branch but had its \"\n> +\t\t\t       \"permissions changed on the other.\"), path);\n> +\n> +\tif (our_blob) {\n> +\t\tprintf(_(\"Removing %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\tremove_path(path);\n> +\t}\n> +\n> +\tif (remove_file_from_index(istate, path))\n> +\t\treturn error(\"%s: cannot remove from the index\", path);\n> +\treturn 0;\n> +}\n> +\n> +static int do_merge_one_file(struct index_state *istate,\n> +\t\t\t     const struct object_id *orig_blob,\n> +\t\t\t     const struct object_id *our_blob,\n> +\t\t\t     const struct object_id *their_blob, const char *path,\n> +\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tint ret, i, dest;\n> +\tssize_t written;\n> +\tmmbuffer_t result = {NULL, 0};\n> +\tmmfile_t mmfs[3];\n> +\txmparam_t xmp = {{0}};\n> +\n> +\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n> +\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n> +\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n> +\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n> +\n> +\tif (orig_blob) {\n> +\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, orig_blob);\n> +\t} else {\n> +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n> +\t\tread_mmblob(mmfs + 0, &null_oid);\n> +\t}\n> +\n> +\tread_mmblob(mmfs + 1, our_blob);\n> +\tread_mmblob(mmfs + 2, their_blob);\n> +\n> +\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n> +\txmp.style = 0;\n> +\txmp.favor = 0;\n> +\n> +\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n> +\n> +\tfor (i = 0; i < 3; i++)\n> +\t\tfree(mmfs[i].ptr);\n> +\n> +\tif (ret < 0) {\n> +\t\tfree(result.ptr);\n> +\t\treturn error(_(\"Failed to execute internal merge\"));\n> +\t}\n> +\n> +\tif (ret > 0 || !orig_blob)\n> +\t\tret = error(_(\"content conflict in %s\"), path);\n> +\tif (our_mode != their_mode)\n> +\t\tret = error(_(\"permission conflict: %o->%o,%o in %s\"),\n> +\t\t\t    orig_mode, our_mode, their_mode, path);\n> +\n> +\tunlink(path);\n> +\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n> +\t\tfree(result.ptr);\n> +\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n> +\t}\n> +\n> +\twritten = write_in_full(dest, result.ptr, result.size);\n> +\tclose(dest);\n> +\n> +\tfree(result.ptr);\n> +\n> +\tif (written < 0)\n> +\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\treturn add_file_to_index(istate, path, 0);\n> +}\n> +\n> +int merge_three_way(struct index_state *istate,\n> +\t\t    const struct object_id *orig_blob,\n> +\t\t    const struct object_id *our_blob,\n> +\t\t    const struct object_id *their_blob, const char *path,\n> +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> +\tif (orig_blob &&\n> +\t    ((!our_blob && !their_blob) ||\n> +\t     (!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n> +\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n> +\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n> +\t\treturn merge_one_file_deleted(istate, our_blob, their_blob, path,\n> +\t\t\t\t\t      orig_mode, our_mode, their_mode);\n> +\t} else if (!orig_blob && our_blob && !their_blob) {\n> +\t\t/*\n> +\t\t * Added in ours.  The other side did not add and we\n> +\t\t * added so there is nothing to be done, except making\n> +\t\t * the path merged.\n> +\t\t */\n> +\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 0);\n> +\t} else if (!orig_blob && !our_blob && their_blob) {\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\tif (file_exists(path))\n> +\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n> +\n> +\t\treturn add_merge_result_to_index(istate, their_mode, their_blob, path, 1);\n> +\t} else if (!orig_blob && our_blob && their_blob &&\n> +\t\t   oideq(our_blob, their_blob)) {\n> +\t\t/* Added in both, identically (check for same permissions). */\n> +\t\tif (our_mode != their_mode)\n> +\t\t\treturn error(_(\"File %s added identically in both branches, \"\n> +\t\t\t\t       \"but permissions conflict %o->%o.\"),\n> +\t\t\t\t     path, our_mode, their_mode);\n> +\n> +\t\tprintf(_(\"Adding %s\\n\"), path);\n> +\n> +\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 1);\n> +\t} else if (our_blob && their_blob) {\n> +\t\t/* Modified in both, but differently. */\n> +\t\treturn do_merge_one_file(istate,\n> +\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n> +\t\t\t\t\t orig_mode, our_mode, their_mode);\n> +\t} else {\n> +\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n> +\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n> +\n> +\t\tif (orig_blob)\n> +\t\t\toid_to_hex_r(orig_hex, orig_blob);\n> +\t\tif (our_blob)\n> +\t\t\toid_to_hex_r(our_hex, our_blob);\n> +\t\tif (their_blob)\n> +\t\t\toid_to_hex_r(their_hex, their_blob);\n> +\n> +\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n> +\t\t\t     path, orig_hex, our_hex, their_hex);\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int merge_one_file_func(struct index_state *istate,\n> +\t\t\tconst struct object_id *orig_blob,\n> +\t\t\tconst struct object_id *our_blob,\n> +\t\t\tconst struct object_id *their_blob, const char *path,\n> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\tvoid *data)\n> +{\n> +\treturn merge_three_way(istate,\n> +\t\t\t       orig_blob, our_blob, their_blob, path,\n> +\t\t\t       orig_mode, our_mode, their_mode);\n> +}\n>\n>  static int merge_entry(struct index_state *istate, int quiet, unsigned int pos,\n>  \t\t       const char *path, int *err, merge_fn fn, void *data)\n> @@ -54,17 +246,24 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n>  \t\t    merge_fn fn, void *data)\n>  {\n>  \tint err = 0, ret;\n> -\tunsigned int i;\n> +\tunsigned int i, prev_nr;\n>\n>  \tfor (i = 0; i < istate->cache_nr; i++) {\n>  \t\tconst struct cache_entry *ce = istate->cache[i];\n>  \t\tif (!ce_stage(ce))\n>  \t\t\tcontinue;\n>\n> +\t\tprev_nr = istate->cache_nr;\n>  \t\tret = merge_entry(istate, quiet || oneshot, i, ce->name, &err, fn, data);\n> -\t\tif (ret > 0)\n> -\t\t\ti += ret - 1;\n> -\t\telse if (ret == -1)\n> +\t\tif (ret > 0) {\n> +\t\t\t/*\n> +\t\t\t * Don't bother handling an index that has\n> +\t\t\t * grown, since merge_one_file_func() can't grow\n> +\t\t\t * it, and merge_one_file_spawn() can't change\n> +\t\t\t * it.\n> +\t\t\t */\n> +\t\t\ti += ret - (prev_nr - istate->cache_nr) - 1;\n> +\t\t} else if (ret == -1)\n>  \t\t\treturn -1;\n>\n>  \t\tif (err && !oneshot)\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> index 88f476f170..8705a550ca 100644\n> --- a/merge-strategies.h\n> +++ b/merge-strategies.h\n> @@ -3,6 +3,12 @@\n>\n>  #include \"object.h\"\n>\n> +int merge_three_way(struct index_state *istate,\n> +\t\t    const struct object_id *orig_blob,\n> +\t\t    const struct object_id *our_blob,\n> +\t\t    const struct object_id *their_blob, const char *path,\n> +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n> +\n>  typedef int (*merge_fn)(struct index_state *istate,\n>  \t\t\tconst struct object_id *orig_blob,\n>  \t\t\tconst struct object_id *our_blob,\n> @@ -10,6 +16,13 @@ typedef int (*merge_fn)(struct index_state *istate,\n>  \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n>  \t\t\tvoid *data);\n>\n> +int merge_one_file_func(struct index_state *istate,\n> +\t\t\tconst struct object_id *orig_blob,\n> +\t\t\tconst struct object_id *our_blob,\n> +\t\t\tconst struct object_id *their_blob, const char *path,\n> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\tvoid *data);\n> +\n>  int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n>  \t\t     const char *path, merge_fn fn, void *data);\n>  int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n> diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\n> index d0cdfeddc1..d9c07965dc 100755\n> --- a/t/t6060-merge-index.sh\n> +++ b/t/t6060-merge-index.sh\n> @@ -72,7 +72,7 @@ test_expect_success 'merge-one-file fails without a work tree' '\n>  \t(cd bare.git &&\n>  \t GIT_INDEX_FILE=$PWD/merge.index &&\n>  \t export GIT_INDEX_FILE &&\n> -\t test_must_fail git merge-index git-merge-one-file -a\n> +\t test_must_fail git merge-index --use=merge-one-file -a\n>  \t)\n>  '\n>\n> diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\n> index 2ce104aca7..075da1f55f 100755\n> --- a/t/t6415-merge-dir-to-symlink.sh\n> +++ b/t/t6415-merge-dir-to-symlink.sh\n> @@ -97,7 +97,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n>  \ttest -h a/b\n>  '\n>\n> -test_expect_failure 'do not lose untracked in merge (resolve)' '\n> +test_expect_success 'do not lose untracked in merge (resolve)' '\n>  \tgit reset --hard &&\n>  \tgit checkout baseline^0 &&\n>  \t>a/b/c/e &&\n> --\n> 2.31.0\n>\n>\n"},{"id":"420064","messageId":"b9d48a96-7e76-8a83-4ca2-c47fca326123@gmail.com","threadId":"53755","inReplyTo":"nycvar.QRO.7.76.6.2103222235150.50@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v7 03/15] t6060: add tests for removed files","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-23T20:43:29Z","receivedAt":"2021-03-23T20:44:15Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Johannes,\n\nLe 22/03/2021 à 22:36, Johannes Schindelin a écrit :\n> Hi Alban,\n> \n> On Wed, 17 Mar 2021, Alban Gruin wrote:\n> \n>> Until now, t6060 did not not check git-mere-one-file's behaviour when a\n> \n> Channeling my inner Eric Sunshine: s/mere-one/merge-one/ ;-)\n> \n\nGood catch.\n\n>> file is deleted in a branch.  To avoid regressions on this during the\n>> conversion, this adds a new file, `file3', in the commit tagged as`base', and\n> \n> Maybe \"during the conversion from shell script to C\"?\n> \n\nI'll rewrite it as \"during the conversion from shell to C\".\n\nCheers,\nAlban\n\n> Other than that, looks good to me! Thanks,\n> Dscho\n> \n\n"},{"id":"420065","messageId":"8fc767c1-2b3e-8fbe-9efb-8e87d862cbfb@gmail.com","threadId":"53755","inReplyTo":"nycvar.QRO.7.76.6.2103222255550.50@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v7 07/15] update-index: move add_cacheinfo() to read-cache.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-23T20:45:13Z","receivedAt":"2021-03-23T20:45:51Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Johannes,\n\nLe 22/03/2021 à 22:59, Johannes Schindelin a écrit :\n> Hi Alban,\n> \n> On Wed, 17 Mar 2021, Alban Gruin wrote:\n> \n>> This moves the function add_cacheinfo() that already exists in\n>> update-index.c to update-index.c, renames it add_to_index_cacheinfo(),\n>> and adds an `istate' parameter.  The new cache entry is returned through\n>> a pointer passed in the parameters.  The return value is either 0\n>> (success), -1 (invalid path), or -2 (failed to add the file in the\n>> index).\n> \n> This paragraph still talks about magic numbers, but the code has constants\n> for them. Maybe elevate the commit message to a more generic description\n> that does not spend time on specifying the exact values, but rather lists\n> the three outcomes in plain English?\n> \n\nOkay, I'll do this.\n\nCheers,\nAlban\n\n> Other than that, this looks fine to me! Thanks,\n> Dscho\n> \n\n"},{"id":"420070","messageId":"c968a6b8-bc0e-04f0-b72d-9fef05b60bd8@gmail.com","threadId":"53755","inReplyTo":"nycvar.QRO.7.76.6.2103222303210.50@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v7 08/15] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-03-23T20:53:41Z","receivedAt":"2021-03-23T20:54:51Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Johannes,\n\nLe 22/03/2021 à 23:20, Johannes Schindelin a écrit :\n> Hi Alban,\n> \n> On Wed, 17 Mar 2021, Alban Gruin wrote:\n> \n\n>> @@ -69,10 +69,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n>>\n>>  \tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n>>  \t\tif (!strcmp(use_internal, \"merge-one-file\"))\n>> -\t\t\tpgm = \"git-merge-one-file\";\n>> +\t\t\tmerge_action = merge_one_file_func;\n>>  \t\telse\n>>  \t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n>> -\t}\n>> +\n>> +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n>> +\t} else\n>> +\t\tmerge_action = merge_one_file_spawn;\n> \n> I would have a slight preference to keep the default initializer, because\n> that makes it easer to reason about. But if you _want_ to keep this patch\n> as-is, I won't object.\n> \n\nYeah, not sure why I did this.  I'll change this.\n\n> It is a bit sad that the conversion cannot be done more incrementally, as\n> there is a lot to unpack in the many different cases that are handled. It\n> looks correct, though.\n> \n> Just one thing:\n> \n>>\n>>  \tfor (; i < argc; i++) {\n>>  \t\tconst char *arg = argv[i];\n>> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n>> new file mode 100644\n>> index 0000000000..ad99c6dbd4\n>> --- /dev/null\n>> +++ b/builtin/merge-one-file.c\n>> @@ -0,0 +1,94 @@\n>> +/*\n>> + * Builtin \"git merge-one-file\"\n>> + *\n>> + * Copyright (c) 2020 Alban Gruin\n>> + *\n>> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n>> + *\n>> + * This is the git per-file merge utility, called with\n>> + *\n>> + *   argv[1] - original file object name (or empty)\n>> + *   argv[2] - file in branch1 object name (or empty)\n>> + *   argv[3] - file in branch2 object name (or empty)\n>> + *   argv[4] - pathname in repository\n>> + *   argv[5] - original file mode (or empty)\n>> + *   argv[6] - file in branch1 mode (or empty)\n>> + *   argv[7] - file in branch2 mode (or empty)\n>> + *\n>> + * Handle some trivial cases. The _really_ trivial cases have been\n>> + * handled already by git read-tree, but that one doesn't do any merges\n>> + * that might change the tree layout.\n>> + */\n>> +\n>> +#include \"cache.h\"\n>> +#include \"builtin.h\"\n>> +#include \"lockfile.h\"\n>> +#include \"merge-strategies.h\"\n>> +\n>> +static const char builtin_merge_one_file_usage[] =\n>> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n>> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n>> +\t\"Blob ids and modes should be empty for missing files.\";\n>> +\n>> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n>> +{\n>> +\tchar *last;\n>> +\tint ret = 0;\n>> +\n>> +\t*mode = strtol(arg, &last, 8);\n>> +\n>> +\tif (*last)\n>> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n>> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n>> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n>> +\n>> +\treturn ret;\n>> +}\n>> +\n>> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n>> +{\n>> +\tstruct object_id orig_blob, our_blob, their_blob,\n>> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n>> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n>> +\tstruct lock_file lock = LOCK_INIT;\n>> +\tstruct repository *r = the_repository;\n>> +\n>> +\tif (argc != 8)\n>> +\t\tusage(builtin_merge_one_file_usage);\n>> +\n>> +\tif (repo_read_index(r) < 0)\n>> +\t\tdie(\"invalid index\");\n>> +\n>> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n>> +\n>> +\tif (!get_oid_hex(argv[1], &orig_blob)) {\n>> +\t\tp_orig_blob = &orig_blob;\n>> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n>> +\t} else if (!*argv[1] && *argv[5])\n>> +\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n> \n> Here, it looks as if the case of an empty `argv[1]` is not handled\n> _explicitly_, but we rely on `get_oid_hex()` to return non-zero, and then\n> we rely on the second arm _also_ not re-assigning `orig_blob`.\n> \n> I wonder whether this could be checked, and whether it would make sense to\n> fold this, along with most of these 5 lines, into the `read_mode()` helper\n> function (DRYing up the code even further).\n> \n\nDo you mean rewriting the first condition to read like this:\n\n    if (*argv[1] && !get_oid_hex(argv[1], &orig_blob)) {\n\n?\n\nIn which case yes, I can do that.\n\nBTW the two lasts calls to read_mode() should be like\n\n    err |= read_mode(…);\n\nCheers,\nAlban\n\n> As for the rest of the patch, it is totally possible that I missed a bug,\n> but it looks correct to me, and the added regression tests give me a good\n> feeling about the patch, too.\n> \n> Thanks,\n> Dscho\n> \n"},{"id":"420073","messageId":"nycvar.QRO.7.76.6.2103232257590.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"20210317204939.17890-10-alban.gruin@gmail.com","subject":"Re: [PATCH v7 09/15] merge-resolve: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-23T22:21:48Z","receivedAt":"2021-03-23T22:24:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Wed, 17 Mar 2021, Alban Gruin wrote:\n\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index 2717af51fd..a51700dae5 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -272,3 +275,95 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n>\n>  \treturn err;\n>  }\n> +\n> +static int fast_forward(struct repository *r, struct tree_desc *t,\n> +\t\t\tint nr, int aggressive)\n> +{\n> +\tstruct unpack_trees_options opts;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\n> +\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n\nShouldn't we lock the index first, and _then_ refresh it? I guess not,\nseeing as we don't do that either in `cmd_status()`: there, we also\nrefresh the index and _then_ lock it.\n\n> +\n> +\tmemset(&opts, 0, sizeof(opts));\n> +\topts.head_idx = 1;\n> +\topts.src_index = r->index;\n> +\topts.dst_index = r->index;\n> +\topts.merge = 1;\n> +\topts.update = 1;\n> +\topts.aggressive = aggressive;\n> +\n> +\tif (nr == 1)\n> +\t\topts.fn = oneway_merge;\n> +\telse if (nr == 2) {\n> +\t\topts.fn = twoway_merge;\n> +\t\topts.initial_checkout = is_index_unborn(r->index);\n> +\t} else if (nr >= 3) {\n> +\t\topts.fn = threeway_merge;\n> +\t\topts.head_idx = nr - 1;\n> +\t}\n\nGiven the function's name `fast_forward()`, I have to admit that I\nsomewhat stumbled over these merges.\n> +\n> +\tif (unpack_trees(nr, t, &opts))\n> +\t\treturn -1;\n> +\n> +\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n> +\t\treturn error(_(\"unable to write new index file\"));\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int add_tree(struct tree *tree, struct tree_desc *t)\n> +{\n> +\tif (parse_tree(tree))\n> +\t\treturn -1;\n> +\n> +\tinit_tree_desc(t, tree->buffer, tree->size);\n> +\treturn 0;\n> +}\n\nThis is a really trivial helper, but it is used a couple times below, so\nit makes sense to have it encapsulated in a separate function.\n\n> +\n> +int merge_strategies_resolve(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remote)\n\nSince it is a list, and since the original variable in the shell script\nhad been named in the plural form, let's do the same here: `remotes`.\n\n> +{\n> +\tstruct tree_desc t[MAX_UNPACK_TREES];\n> +\tstruct object_id head, oid;\n> +\tstruct commit_list *i;\n> +\tint nr = 0;\n> +\n> +\tif (head_arg)\n> +\t\tget_oid(head_arg, &head);\n> +\n> +\tputs(_(\"Trying simple merge.\"));\n\nGood. Usually I would recommend to print this to `stderr`, but the\noriginal script prints it to `stdout`, so we should do that here, too.\n\n> +\n> +\tfor (i = bases; i && i->item; i = i->next) {\n> +\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n> +\t\t\treturn 2;\n\nSince we're talking about a library function, not a `cmd_*()` function,\nthe return value on error should probably be negative.\n\nEven better would be to let the function return an `enum` that contains\nlabels with more intuitive meaning than \"2\".\n\nIt _is_ the expected exit code when calling `git merge-resolve`, of course\n(because of the `|| exit 2` after that `read-tree` call), but I wonder\nwhether a better layer for that `2` would be the `cmd_merge_resolve()`\nfunction, letting `merge_strategies_resolve()` report failures in a more\nfine-grained fashion.\n\n> +\t}\n> +\n> +\tif (head_arg) {\n\nIt would probably be easier to read if the `if (head_arg)` clause above\nwas merged into this here clause.\n\n> +\t\tstruct tree *tree = parse_tree_indirect(&head);\n> +\t\tif (add_tree(tree, t + (nr++)))\n> +\t\t\treturn 2;\n> +\t}\n> +\n> +\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n> +\t\treturn 2;\n\nYou get away with assuming that `remotes` only contains at most a single\nentry because `cmd_merge_resolve()` verified it.\n\nHowever, as the intention is to use this as a library function, I think\nthe input validation needs to be moved here instead of relying on all\ncallers to verify that they send at most one \"remote\" ref.\n\nOther than that, this patch looks good to me.\n\nThanks,\nDscho\n\n> +\n> +\tif (fast_forward(r, t, nr, 1))\n> +\t\treturn 2;\n> +\n> +\tif (write_index_as_tree(&oid, r->index, r->index_file,\n> +\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n> +\t\tint ret;\n> +\t\tstruct lock_file lock = LOCK_INIT;\n> +\n> +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n> +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n> +\t\tret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n> +\n> +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n> +\t\treturn !!ret;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> index 8705a550ca..bba4bf999c 100644\n> --- a/merge-strategies.h\n> +++ b/merge-strategies.h\n> @@ -1,6 +1,7 @@\n>  #ifndef MERGE_STRATEGIES_H\n>  #define MERGE_STRATEGIES_H\n>\n> +#include \"commit.h\"\n>  #include \"object.h\"\n>\n>  int merge_three_way(struct index_state *istate,\n> @@ -28,4 +29,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n>  int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n>  \t\t    merge_fn fn, void *data);\n>\n> +int merge_strategies_resolve(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remote);\n> +\n>  #endif /* MERGE_STRATEGIES_H */\n> --\n> 2.31.0\n>\n>\n"},{"id":"420077","messageId":"nycvar.QRO.7.76.6.2103232323330.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"20210317204939.17890-12-alban.gruin@gmail.com","subject":"Re: [PATCH v7 11/15] merge-octopus: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-23T23:58:38Z","receivedAt":"2021-03-23T23:59:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Wed, 17 Mar 2021, Alban Gruin wrote:\n\n> This rewrites `git merge-octopus' from shell to C.  As for the two last\n> conversions, this port removes calls to external processes to avoid\n> reading and writing the index over and over again.\n>\n>  - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n>    unpack_trees().\n>\n>  - The call to `write-tree' is replaced by a call to\n>    write_index_as_tree().\n>\n>  - The call to `diff-index ...' is replaced by a call to\n>    repo_index_has_changes().\n>\n>  - The call to `merge-index', needed to invoke `git merge-one-file', is\n>    replaced by a call to merge_all_index().\n>\n> The index is read in cmd_merge_octopus(), and is wrote back by\n\ns/wrote/written/\n\n> merge_strategies_octopus().\n\nI wonder why, though. Maybe the commit message could clarify that?\n\n> Here to, merge_strategies_octopus() takes two commit lists and a string\n\ns/to,/too,/\n\n> to reduce frictions when try_merge_strategies() will be modified to call\n\ns/frictions/friction/\n\n> it directly.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>\n> [...]\n> diff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\n> new file mode 100644\n> index 0000000000..9b9939b6b2\n> --- /dev/null\n> +++ b/builtin/merge-octopus.c\n> @@ -0,0 +1,70 @@\n> +/*\n> + * Builtin \"git merge-octopus\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-octopus.sh, written by Junio C Hamano.\n> + *\n> + * Resolve two or more trees.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"commit.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_octopus_usage[] =\n> +\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n> +\n> +int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint i, sep_seen = 0;\n> +\tstruct commit_list *bases = NULL, *remotes = NULL;\n> +\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n> +\tconst char *head_arg = NULL;\n> +\tstruct repository *r = the_repository;\n> +\n> +\tif (argc < 5)\n> +\t\tusage(builtin_merge_octopus_usage);\n> +\n> +\tsetup_work_tree();\n> +\tif (repo_read_index(r) < 0)\n> +\t\tdie(\"invalid index\");\n> +\n> +\t/*\n> +\t * The first parameters up to -- are merge bases; the rest are\n> +\t * heads.\n> +\t */\n> +\tfor (i = 1; i < argc; i++) {\n> +\t\tif (strcmp(argv[i], \"--\") == 0)\n> +\t\t\tsep_seen = 1;\n> +\t\telse if (strcmp(argv[i], \"-h\") == 0)\n> +\t\t\tusage(builtin_merge_octopus_usage);\n> +\t\telse if (sep_seen && !head_arg)\n> +\t\t\thead_arg = argv[i];\n> +\t\telse {\n> +\t\t\tstruct object_id oid;\n> +\t\t\tstruct commit *commit;\n> +\n> +\t\t\tif (get_oid(argv[i], &oid))\n> +\t\t\t\tdie(\"object %s not found.\", argv[i]);\n> +\n> +\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n> +\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n> +\n> +\t\t\tif (sep_seen)\n> +\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n> +\t\t\telse\n> +\t\t\t\tnext_base = commit_list_append(commit, next_base);\n> +\t\t}\n> +\t}\n> +\n> +\t/*\n> +\t * Reject if this is not an octopus -- resolve should be used\n> +\t * instead.\n> +\t */\n> +\tif (commit_list_count(remotes) < 2)\n> +\t\treturn 2;\n\nAs with `merge-resolve`, I would suggest to:\n\n- move this input validation down to `merge_strategies_octopus()`, and\n- change that function's signature to return an `enum`, and then\n- make sure that that `enum` uses easy-to-understand labels.\n\n> +\n> +\treturn merge_strategies_octopus(r, bases, head_arg, remotes);\n> +}\n>\n> [...]\n>\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index a51700dae5..ebc0d0b1e2 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -367,3 +368,177 @@ int merge_strategies_resolve(struct repository *r,\n>\n>  \treturn 0;\n>  }\n> +\n> +static int write_tree(struct repository *r, struct tree **reference_tree)\n> +{\n> +\tstruct object_id oid;\n> +\tint ret;\n> +\n> +\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file,\n> +\t\t\t\t\tWRITE_TREE_SILENT, NULL)))\n> +\t\t*reference_tree = lookup_tree(r, &oid);\n> +\n> +\treturn ret;\n> +}\n> +\n> +static int octopus_fast_forward(struct repository *r, const char *branch_name,\n> +\t\t\t\tstruct tree *tree_head, struct tree *current_tree,\n> +\t\t\t\tstruct tree **reference_tree)\n\nWhile I objected to the name of the `fast_forward()` function, I think the\n`octopus_fast_forward()` function is named aptly.\n\n> +{\n> +\t/*\n> +\t * The first head being merged was a fast-forward.  Advance the\n> +\t * reference commit to the head being merged, and use that tree\n> +\t * as the intermediate result of the merge.  We still need to\n> +\t * count this as part of the parent set.\n> +\t */\n> +\tstruct tree_desc t[2];\n> +\n> +\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n> +\n> +\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n> +\tif (add_tree(current_tree, t + 1))\n> +\t\treturn -1;\n> +\tif (fast_forward(r, t, 2, 0))\n> +\t\treturn -1;\n> +\tif (write_tree(r, reference_tree))\n> +\t\treturn -1;\n> +\n> +\treturn 0;\n> +}\n> +\n> +static int octopus_do_merge(struct repository *r, const char *branch_name,\n> +\t\t\t    struct commit_list *common, struct tree *current_tree,\n> +\t\t\t    struct tree **reference_tree)\n> +{\n> +\tstruct tree_desc t[MAX_UNPACK_TREES];\n> +\tstruct commit_list *i;\n> +\tint nr = 0, ret = 0;\n> +\n> +\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n> +\n> +\tfor (i = common; i; i = i->next) {\n> +\t\tstruct tree *tree = repo_get_commit_tree(r, i->item);\n> +\t\tif (add_tree(tree, t + (nr++)))\n> +\t\t\treturn -1;\n> +\t}\n> +\n> +\tif (add_tree(*reference_tree, t + (nr++)))\n> +\t\treturn -1;\n> +\tif (add_tree(current_tree, t + (nr++)))\n> +\t\treturn -1;\n> +\tif (fast_forward(r, t, nr, 1))\n> +\t\treturn 2;\n> +\n> +\tif (write_tree(r, reference_tree)) {\n> +\t\tstruct lock_file lock = LOCK_INIT;\n> +\n> +\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n> +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n\nIt is a bit funny to see this as the only time in this patch where the\nindex is locked, and it is immediately released thereafter.\n\nI would have expected the lock to be taken first thing in\n`merge_strategies_octopus()` and then being committed only on success, or\non failure to merge.\n\n> +\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, NULL);\n> +\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n> +\n> +\t\twrite_tree(r, reference_tree);\n> +\t}\n> +\n> +\treturn ret;\n> +}\n> +\n> +int merge_strategies_octopus(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remotes)\n> +{\n> +\tint ff_merge = 1, ret = 0, nr_references = 1;\n> +\tstruct commit **reference_commits, *head_commit;\n> +\tstruct tree *reference_tree, *head_tree;\n> +\tstruct commit_list *i;\n> +\tstruct object_id head;\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\n> +\tget_oid(head_arg, &head);\n> +\thead_commit = lookup_commit_reference(r, &head);\n> +\thead_tree = repo_get_commit_tree(r, head_commit);\n> +\n> +\tif (parse_tree(head_tree))\n> +\t\treturn 2;\n> +\n> +\tif (repo_index_has_changes(r, head_tree, &sb)) {\n> +\t\terror(_(\"Your local changes to the following files \"\n> +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n> +\t\t      sb.buf);\n> +\t\tstrbuf_release(&sb);\n> +\t\treturn 2;\n> +\t}\n> +\n> +\tCALLOC_ARRAY(reference_commits, commit_list_count(remotes) + 1);\n> +\treference_commits[0] = head_commit;\n> +\treference_tree = head_tree;\n> +\n> +\tfor (i = remotes; i && i->item; i = i->next) {\n> +\t\tstruct commit *c = i->item;\n> +\t\tstruct object_id *oid = &c->object.oid;\n> +\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n> +\t\tstruct commit_list *common, *j;\n> +\t\tchar *branch_name = merge_get_better_branch_name(oid_to_hex(oid));\n> +\t\tint up_to_date = 0;\n> +\n> +\t\tcommon = repo_get_merge_bases_many(r, c, nr_references, reference_commits);\n> +\t\tif (!common) {\n> +\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n> +\n> +\t\t\tfree(branch_name);\n> +\t\t\tfree_commit_list(common);\n> +\t\t\tfree(reference_commits);\n> +\n> +\t\t\treturn 2;\n> +\t\t}\n> +\n> +\t\tfor (j = common; j && !up_to_date && ff_merge; j = j->next) {\n> +\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n\nSemantically, I would argue that this is an `||=`, not `|=`: we want a\nBoolean \"or\", not a bit-wise one.\n\n> +\n> +\t\t\tif (!j->next &&\n> +\t\t\t    !oideq(&j->item->object.oid,\n> +\t\t\t\t   &reference_commits[nr_references - 1]->object.oid))\n> +\t\t\t\tff_merge = 0;\n> +\t\t}\n\nHmm. This is combining two things into the same loop, with a combined loop\ncondition. The two things are:\n\n\tcase \"$LF$common$LF\" in\n        *\"$LF$SHA1$LF\"*)\n                eval_gettextln \"Already up to date with \\$pretty_name\"\n                continue\n                ;;\n        esac\n\n        if test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n        then\n                # The first head being merged was a fast-forward.\n                # Advance MRC to the head being merged, and use that\n                # tree as the intermediate result of the merge.\n                # We still need to count this as part of the parent set.\n\n                eval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n                git read-tree -u -m $head $SHA1 || exit\n                MRC=$SHA1 MRT=$(git write-tree)\n                continue\n        fi\n\n        NON_FF_MERGE=1\n\nThe first one tries to verify that the `common` list contains `oid`. The C\ncode does this, too, using the intuitive variable name `up_to_date`, which\nis good.\n\nNow, big question: is there a way for the loop to exit before we had a\nchance to see the common commit that is identical to `oid`? And I think\nthere is: `ff_merge` is not reset between the outer loop (the one\niterating over `remotes`). If that is the case, then we would miss that\nwe're already up to date.\n\nNext thing is that `if test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"` thing.\nThis is turned into that `if (!j->next && ...)` thing, and I _think_ that\nit does the wrong thing. Rather than verifying that the `common` list\nis identical to \"MRC\" (= the merge reference list), it would only ever\ncompare the last entries of `common` and MRC.\n\nI have a hard time convincing myself that this is idempotent to the shell\nscript version.\n\nInstead, I think it should read somewhat like this:\n\n\t\tfor (j = common, k = 0; j && (!up_to_date || ff_merge); j = j->next) {\n\t\t\tup_to_date ||= oideq(&j->item->object.oid, oid);\n\n\t\t\tif (ff_merge &&\n\t\t\t    (k >= nr_references ||\n\t\t\t     !oideq(&j->item->object.oid,\n\t\t\t\t    &reference_commits[k++]->object.oid))\n\t\t\t\tff_merge = 0;\n\t\t}\n\nBut quite honestly, this still looks \"too clever\" and too fragile to me.\nFor something as rare as an octopus merge, I'd _much_ rather have simpler\ncode that is easy to reason about and does the job reliably (if somewhat\nslower than a hyper-optimized version):\n\n\t\t/*\n\t\t * If `oid` is reachable from `HEAD`, we're already up to\n\t\t * date.\n\t\t */\n\t\tfor (j = common; j; j = j->next)\n\t\t\tif (oideq(&j->item->object.oid, oid)) {\n\t\t\t\tup_to_date = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\tif (up_to_date) {\n\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n\n\t\t\tfree(branch_name);\n\t\t\tfree_commit_list(common);\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (j = common, k = 0; ff_merge && j; j = j->next)\n\t\t\tif (k >= nr_references ||\n\t\t\t    !oideq(&j->item->object.oid,\n\t\t\t\t   &reference_commits[k++]->object.oid))\n\t\t\t\tff_merge = 0;\n\t\tif (k != nr_references)\n\t\t\tff_merge = 0;\n\n\nBut the more I stare at the shell script code, the more I start to believe\nthat this `MRC` business is just a very convoluted way to essentially\nverify that the `HEAD` is the _single_ merge base.\n\nI say that because I cannot fail to notice that `$common` separates the\nmerge bases by newlines, while `$MRC` separates its entries by spaces.\nTherefore,\n\n\t\ttest \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n\ncan only ever evaluate to `true` if both `$common` and `$MRC` contains\nexactly one and the same oid, namely the one of the revision to which we\njust fast-forwarded in the previous iteration.\n\nTherefore, the logic does not even need a loop. It would be as trivial as:\n\n\t\t/*\n\t\t * If we could fast-forward so far and `HEAD` is the\n\t\t * single merge base with the current `remote` revision,\n\t\t * keep fast-forwarding.\n\t\t */\n\t\tif (ff_merge && common && !common->next && nr_references == 1 &&\n\t\t    oideq(common->item->object.oid,\n\t\t\t  reference_commit[0]->object.oid)) {\n\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n\t\t\t\t\t\t   current_tree, &reference_tree);\n\t\t\tnr_references = 0;\n\t\t} else {\n\t\t\tff_merge = 0;\n\t\t\tret = octopus_do_merge(r, branch_name, common,\n\t\t\t\t\t       current_tree, &reference_tree);\n\t\t}\n\n\n> +\n> +\t\tif (up_to_date) {\n> +\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n> +\n> +\t\t\tfree(branch_name);\n> +\t\t\tfree_commit_list(common);\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n> +\t\tif (ff_merge) {\n> +\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n> +\t\t\t\t\t\t   current_tree, &reference_tree);\n> +\t\t\tnr_references = 0;\n> +\t\t} else {\n> +\t\t\tret = octopus_do_merge(r, branch_name, common,\n> +\t\t\t\t\t       current_tree, &reference_tree);\n> +\t\t}\n> +\n> +\t\tfree(branch_name);\n> +\t\tfree_commit_list(common);\n> +\n> +\t\tif (ret == -1 || ret == 2)\n> +\t\t\tbreak;\n> +\t\telse if (ret && i->next) {\n> +\t\t\t/*\n> +\t\t\t * We allow only last one to have a\n> +\t\t\t * hand-resolvable conflicts.  Last round failed\n> +\t\t\t * and we still had a head to merge.\n> +\t\t\t */\n> +\t\t\tputs(_(\"Automated merge did not work.\"));\n> +\t\t\tputs(_(\"Should not be doing an octopus.\"));\n> +\n> +\t\t\tfree(reference_commits);\n> +\t\t\treturn 2;\n\nI see that you moved this block from the beginning of the loop to the end\n(in the script, it was at the start of the loop). This is a good change.\n\nI wonder, though, whether it wouldn't make more sense to replace the last\ntwo lines with this:\n\n\t\t\tret = 2;\n\t\t\tbreak;\n\nThat way, we need not worry about releasing resources in multiple places\nin the future: it will all be done at the end of the function.\n\nPhew. What a lot to unpack.\n\nPlease let me express my gratitude for working on this. My many comments\nmay seem as if I am unhappy with the progress, but nothing could be\nfurther from the truth. I am impressed by your tenacity, and I hope that I\ncould do my little bit to make this patch series as good as we can.\n\nThanks,\nDscho\n\n> +\t\t}\n> +\n> +\t\treference_commits[nr_references++] = c;\n> +\t}\n> +\n> +\tfree(reference_commits);\n> +\treturn ret;\n> +}\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> index bba4bf999c..8de2249ee6 100644\n> --- a/merge-strategies.h\n> +++ b/merge-strategies.h\n> @@ -32,5 +32,8 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n>  int merge_strategies_resolve(struct repository *r,\n>  \t\t\t     struct commit_list *bases, const char *head_arg,\n>  \t\t\t     struct commit_list *remote);\n> +int merge_strategies_octopus(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remote);\n>\n>  #endif /* MERGE_STRATEGIES_H */\n> --\n> 2.31.0\n>\n>\n"},{"id":"420110","messageId":"nycvar.QRO.7.76.6.2103241004590.50@tvgsbejvaqbjf.bet","threadId":"53755","inReplyTo":"c968a6b8-bc0e-04f0-b72d-9fef05b60bd8@gmail.com","subject":"Re: [PATCH v7 08/15] merge-one-file: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-03-24T09:10:24Z","receivedAt":"2021-03-24T10:58:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Tue, 23 Mar 2021, Alban Gruin wrote:\n\n> Le 22/03/2021 à 23:20, Johannes Schindelin a écrit :\n> >\n> > On Wed, 17 Mar 2021, Alban Gruin wrote:\n> >\n> >>\n> >>  \tfor (; i < argc; i++) {\n> >>  \t\tconst char *arg = argv[i];\n> >> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> >> new file mode 100644\n> >> index 0000000000..ad99c6dbd4\n> >> --- /dev/null\n> >> +++ b/builtin/merge-one-file.c\n> >> @@ -0,0 +1,94 @@\n> >> +/*\n> >> + * Builtin \"git merge-one-file\"\n> >> + *\n> >> + * Copyright (c) 2020 Alban Gruin\n> >> + *\n> >> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n> >> + *\n> >> + * This is the git per-file merge utility, called with\n> >> + *\n> >> + *   argv[1] - original file object name (or empty)\n> >> + *   argv[2] - file in branch1 object name (or empty)\n> >> + *   argv[3] - file in branch2 object name (or empty)\n> >> + *   argv[4] - pathname in repository\n> >> + *   argv[5] - original file mode (or empty)\n> >> + *   argv[6] - file in branch1 mode (or empty)\n> >> + *   argv[7] - file in branch2 mode (or empty)\n> >> + *\n> >> + * Handle some trivial cases. The _really_ trivial cases have been\n> >> + * handled already by git read-tree, but that one doesn't do any merges\n> >> + * that might change the tree layout.\n> >> + */\n> >> +\n> >> +#include \"cache.h\"\n> >> +#include \"builtin.h\"\n> >> +#include \"lockfile.h\"\n> >> +#include \"merge-strategies.h\"\n> >> +\n> >> +static const char builtin_merge_one_file_usage[] =\n> >> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n> >> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n> >> +\t\"Blob ids and modes should be empty for missing files.\";\n> >> +\n> >> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n> >> +{\n> >> +\tchar *last;\n> >> +\tint ret = 0;\n> >> +\n> >> +\t*mode = strtol(arg, &last, 8);\n> >> +\n> >> +\tif (*last)\n> >> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n> >> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n> >> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n> >> +\n> >> +\treturn ret;\n> >> +}\n> >> +\n> >> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n> >> +{\n> >> +\tstruct object_id orig_blob, our_blob, their_blob,\n> >> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n> >> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n> >> +\tstruct lock_file lock = LOCK_INIT;\n> >> +\tstruct repository *r = the_repository;\n> >> +\n> >> +\tif (argc != 8)\n> >> +\t\tusage(builtin_merge_one_file_usage);\n> >> +\n> >> +\tif (repo_read_index(r) < 0)\n> >> +\t\tdie(\"invalid index\");\n> >> +\n> >> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n> >> +\n> >> +\tif (!get_oid_hex(argv[1], &orig_blob)) {\n> >> +\t\tp_orig_blob = &orig_blob;\n> >> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n> >> +\t} else if (!*argv[1] && *argv[5])\n> >> +\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n> >\n> > Here, it looks as if the case of an empty `argv[1]` is not handled\n> > _explicitly_, but we rely on `get_oid_hex()` to return non-zero, and then\n> > we rely on the second arm _also_ not re-assigning `orig_blob`.\n> >\n> > I wonder whether this could be checked, and whether it would make sense to\n> > fold this, along with most of these 5 lines, into the `read_mode()` helper\n> > function (DRYing up the code even further).\n> >\n>\n> Do you mean rewriting the first condition to read like this:\n>\n>     if (*argv[1] && !get_oid_hex(argv[1], &orig_blob)) {\n>\n> ?\n>\n> In which case yes, I can do that.\n\nYes, that's what I meant. Or this instead:\n\n\tif (!*argv[1]) {\n\t\tif (*argv[5])\n\t\t\tret = error(... mode was still given ...)\n\t} else if (!get_oid_hex(...)) {\n\t\t...\n\t}\n\n> BTW the two lasts calls to read_mode() should be like\n>\n>     err |= read_mode(…);\n\nWhile this is certainly shorter than\n\n\tif (read_mode(...))\n\t\tret = -1;\n\nI actually prefer the latter, for clarity (we do want `read_mode()` to be\ncalled, i.e. we cannot use `||=` here, but it is also not a bit-wise \"or\"\noperation, therefore `|=` strikes me as misleading). What do you think?\n\nCiao,\nDscho\n"},{"id":"421533","messageId":"23f47974-36e2-7d28-49c0-e6ddc06c75a1@gmail.com","threadId":"53755","inReplyTo":"nycvar.QRO.7.76.6.2103241004590.50@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v7 08/15] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-04-10T14:17:08Z","receivedAt":"2021-04-10T14:17:26Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Johannes,\n\nLe 24/03/2021 à 10:10, Johannes Schindelin a écrit :\n> Hi Alban,\n> \n> On Tue, 23 Mar 2021, Alban Gruin wrote:\n> \n>> Le 22/03/2021 à 23:20, Johannes Schindelin a écrit :\n>>>\n>>> On Wed, 17 Mar 2021, Alban Gruin wrote:\n>>>\n>>>>\n>>>>  \tfor (; i < argc; i++) {\n>>>>  \t\tconst char *arg = argv[i];\n>>>> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n>>>> new file mode 100644\n>>>> index 0000000000..ad99c6dbd4\n>>>> --- /dev/null\n>>>> +++ b/builtin/merge-one-file.c\n>>>> @@ -0,0 +1,94 @@\n>>>> +/*\n>>>> + * Builtin \"git merge-one-file\"\n>>>> + *\n>>>> + * Copyright (c) 2020 Alban Gruin\n>>>> + *\n>>>> + * Based on git-merge-one-file.sh, written by Linus Torvalds.\n>>>> + *\n>>>> + * This is the git per-file merge utility, called with\n>>>> + *\n>>>> + *   argv[1] - original file object name (or empty)\n>>>> + *   argv[2] - file in branch1 object name (or empty)\n>>>> + *   argv[3] - file in branch2 object name (or empty)\n>>>> + *   argv[4] - pathname in repository\n>>>> + *   argv[5] - original file mode (or empty)\n>>>> + *   argv[6] - file in branch1 mode (or empty)\n>>>> + *   argv[7] - file in branch2 mode (or empty)\n>>>> + *\n>>>> + * Handle some trivial cases. The _really_ trivial cases have been\n>>>> + * handled already by git read-tree, but that one doesn't do any merges\n>>>> + * that might change the tree layout.\n>>>> + */\n>>>> +\n>>>> +#include \"cache.h\"\n>>>> +#include \"builtin.h\"\n>>>> +#include \"lockfile.h\"\n>>>> +#include \"merge-strategies.h\"\n>>>> +\n>>>> +static const char builtin_merge_one_file_usage[] =\n>>>> +\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n>>>> +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n>>>> +\t\"Blob ids and modes should be empty for missing files.\";\n>>>> +\n>>>> +static int read_mode(const char *name, const char *arg, unsigned int *mode)\n>>>> +{\n>>>> +\tchar *last;\n>>>> +\tint ret = 0;\n>>>> +\n>>>> +\t*mode = strtol(arg, &last, 8);\n>>>> +\n>>>> +\tif (*last)\n>>>> +\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n>>>> +\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n>>>> +\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n>>>> +\n>>>> +\treturn ret;\n>>>> +}\n>>>> +\n>>>> +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n>>>> +{\n>>>> +\tstruct object_id orig_blob, our_blob, their_blob,\n>>>> +\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n>>>> +\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n>>>> +\tstruct lock_file lock = LOCK_INIT;\n>>>> +\tstruct repository *r = the_repository;\n>>>> +\n>>>> +\tif (argc != 8)\n>>>> +\t\tusage(builtin_merge_one_file_usage);\n>>>> +\n>>>> +\tif (repo_read_index(r) < 0)\n>>>> +\t\tdie(\"invalid index\");\n>>>> +\n>>>> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n>>>> +\n>>>> +\tif (!get_oid_hex(argv[1], &orig_blob)) {\n>>>> +\t\tp_orig_blob = &orig_blob;\n>>>> +\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n>>>> +\t} else if (!*argv[1] && *argv[5])\n>>>> +\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n>>>\n>>> Here, it looks as if the case of an empty `argv[1]` is not handled\n>>> _explicitly_, but we rely on `get_oid_hex()` to return non-zero, and then\n>>> we rely on the second arm _also_ not re-assigning `orig_blob`.\n>>>\n>>> I wonder whether this could be checked, and whether it would make sense to\n>>> fold this, along with most of these 5 lines, into the `read_mode()` helper\n>>> function (DRYing up the code even further).\n>>>\n>>\n>> Do you mean rewriting the first condition to read like this:\n>>\n>>     if (*argv[1] && !get_oid_hex(argv[1], &orig_blob)) {\n>>\n>> ?\n>>\n>> In which case yes, I can do that.\n> \n> Yes, that's what I meant. Or this instead:\n> \n> \tif (!*argv[1]) {\n> \t\tif (*argv[5])\n> \t\t\tret = error(... mode was still given ...)\n> \t} else if (!get_oid_hex(...)) {\n> \t\t...\n> \t}\n> \n>> BTW the two lasts calls to read_mode() should be like\n>>\n>>     err |= read_mode(…);\n> \n> While this is certainly shorter than\n> \n> \tif (read_mode(...))\n> \t\tret = -1;\n> \n\nSo, I folded all of this into a single function that reads the mode,\nconvert the oid, and show an error if needed.  Now, I have:\n\n    if (read_param(\"orig\", argv[1], argv[5], &orig_blob,\n                   &p_orig_blob, &orig_mode))\n        ret = -1;\n\n    if (read_param(\"our\", …))\n        ret = -1;\n\n    if (read_param(\"their\", …))\n        ret = -1;\n\n    if (ret)\n        return ret;\n\n\n> I actually prefer the latter, for clarity (we do want `read_mode()` to be\n> called, i.e. we cannot use `||=` here, but it is also not a bit-wise \"or\"\n> operation, therefore `|=` strikes me as misleading). What do you think?\n> \n\nYes, I think it's much clearer that way.\n\nFIY, `||=' does not exist in C.\n\nCheers,\nAlban\n\n> Ciao,\n> Dscho\n> \n\n"},{"id":"421534","messageId":"025aad24-68e3-295b-1e3b-2a7250807276@gmail.com","threadId":"53755","inReplyTo":"nycvar.QRO.7.76.6.2103232257590.50@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v7 09/15] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2021-04-10T14:17:32Z","receivedAt":"2021-04-10T14:17:37Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Johannes,\n\nLe 23/03/2021 à 23:21, Johannes Schindelin a écrit :\n> Hi Alban,\n> \n> On Wed, 17 Mar 2021, Alban Gruin wrote:\n> \n>> diff --git a/merge-strategies.c b/merge-strategies.c\n>> index 2717af51fd..a51700dae5 100644\n>> --- a/merge-strategies.c\n>> +++ b/merge-strategies.c\n>> @@ -272,3 +275,95 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n>>\n>>  \treturn err;\n>>  }\n>> +\n>> +static int fast_forward(struct repository *r, struct tree_desc *t,\n>> +\t\t\tint nr, int aggressive)\n>> +{\n>> +\tstruct unpack_trees_options opts;\n>> +\tstruct lock_file lock = LOCK_INIT;\n>> +\n>> +\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n>> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n> \n> Shouldn't we lock the index first, and _then_ refresh it? I guess not,\n> seeing as we don't do that either in `cmd_status()`: there, we also\n> refresh the index and _then_ lock it.\n> \n\nYeah, I don't think I saw a lock/refresh sequence, but I may be wrong.\n\n>> +\n>> +\tmemset(&opts, 0, sizeof(opts));\n>> +\topts.head_idx = 1;\n>> +\topts.src_index = r->index;\n>> +\topts.dst_index = r->index;\n>> +\topts.merge = 1;\n>> +\topts.update = 1;\n>> +\topts.aggressive = aggressive;\n>> +\n>> +\tif (nr == 1)\n>> +\t\topts.fn = oneway_merge;\n>> +\telse if (nr == 2) {\n>> +\t\topts.fn = twoway_merge;\n>> +\t\topts.initial_checkout = is_index_unborn(r->index);\n>> +\t} else if (nr >= 3) {\n>> +\t\topts.fn = threeway_merge;\n>> +\t\topts.head_idx = nr - 1;\n>> +\t}\n> \n> Given the function's name `fast_forward()`, I have to admit that I\n> somewhat stumbled over these merges.\n>> +\n>> +\tif (unpack_trees(nr, t, &opts))\n>> +\t\treturn -1;\n>> +\n\nI just noticed that the lock is not released if there is an error here.\n\n>> +\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n>> +\t\treturn error(_(\"unable to write new index file\"));\n>> +\n>> +\treturn 0;\n>> +}\n>> +\n>> +static int add_tree(struct tree *tree, struct tree_desc *t)\n>> +{\n>> +\tif (parse_tree(tree))\n>> +\t\treturn -1;\n>> +\n>> +\tinit_tree_desc(t, tree->buffer, tree->size);\n>> +\treturn 0;\n>> +}\n> \n> This is a really trivial helper, but it is used a couple times below, so\n> it makes sense to have it encapsulated in a separate function.\n> \n>> +\n>> +int merge_strategies_resolve(struct repository *r,\n>> +\t\t\t     struct commit_list *bases, const char *head_arg,\n>> +\t\t\t     struct commit_list *remote)\n> \n> Since it is a list, and since the original variable in the shell script\n> had been named in the plural form, let's do the same here: `remotes`.\n> \n\nThis one is supposed to contain only one commit, so I'm not really\nconviced that this parameter should be in the plural form.\n\n>> +{\n>> +\tstruct tree_desc t[MAX_UNPACK_TREES];\n>> +\tstruct object_id head, oid;\n>> +\tstruct commit_list *i;\n>> +\tint nr = 0;\n>> +\n>> +\tif (head_arg)\n>> +\t\tget_oid(head_arg, &head);\n>> +\n>> +\tputs(_(\"Trying simple merge.\"));\n> \n> Good. Usually I would recommend to print this to `stderr`, but the\n> original script prints it to `stdout`, so we should do that here, too.\n> \n>> +\n>> +\tfor (i = bases; i && i->item; i = i->next) {\n>> +\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n>> +\t\t\treturn 2;\n> \n> Since we're talking about a library function, not a `cmd_*()` function,\n> the return value on error should probably be negative.\n> \n> Even better would be to let the function return an `enum` that contains\n> labels with more intuitive meaning than \"2\".\n> \n> It _is_ the expected exit code when calling `git merge-resolve`, of course\n> (because of the `|| exit 2` after that `read-tree` call), but I wonder\n> whether a better layer for that `2` would be the `cmd_merge_resolve()`\n> function, letting `merge_strategies_resolve()` report failures in a more\n> fine-grained fashion.\n> \n\nRight -- I'll see what I can do here.\n\n>> +\t}\n>> +\n>> +\tif (head_arg) {\n> \n> It would probably be easier to read if the `if (head_arg)` clause above\n> was merged into this here clause.\n> \n>> +\t\tstruct tree *tree = parse_tree_indirect(&head);\n>> +\t\tif (add_tree(tree, t + (nr++)))\n>> +\t\t\treturn 2;\n>> +\t}\n>> +\n>> +\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n>> +\t\treturn 2;\n> \n> You get away with assuming that `remotes` only contains at most a single\n> entry because `cmd_merge_resolve()` verified it.\n> \n> However, as the intention is to use this as a library function, I think\n> the input validation needs to be moved here instead of relying on all\n> callers to verify that they send at most one \"remote\" ref.\n> \n> Other than that, this patch looks good to me.\n> \nWell, this condition checks that there is one commit, and if so, uses it\nto call add_tree().  I don't see the mistake here.\n\nCheers,\nAlban\n\n> Thanks,\n> Dscho\n> \n\n\n"},{"id":"460931","messageId":"20220809185429.20098-2-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 01/14] t6060: modify multiple files to expose a possible issue with merge-index","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:16Z","receivedAt":"2022-08-09T19:09:30Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Currently, merge-index iterates over every index entry, skipping stage0\nentries.  It will then count how many entries following the current one\nhave the same name, then fork to do the merge.  It will then increase\nthe iterator by the number of entries to skip them.  This behaviour is\ncorrect, as even if the subprocess modifies the index, merge-index does\nnot reload it at all.\n\nBut when it will be rewritten to use a function, the index it will use\nwill be modified and may shrink when a conflict happens or if a file is\nremoved, so we have to be careful to handle such cases.\n\nHere is an example:\n\n *    Merge branches, file1 and file2 are trivially mergeable.\n |\\\n | *  Modifies file1 and file2.\n * |  Modifies file1 and file2.\n |/\n *    Adds file1 and file2.\n\nWhen the merge happens, the index will look like that:\n\n i -> 0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n      3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\nmerge-index handles `file1' first.  As it appears 3 times after the\niterator, it is merged.  The index is now stale, `i' is increased by 3,\nand the index now looks like this:\n\n      0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n i -> 3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\n`file2' appears three times too, so it is merged.\n\nWith a naive rewrite, the index would look like this:\n\n      0. file1 (stage0)\n      1. file2 (stage1)\n      2. file2 (stage2)\n i -> 3. file2 (stage3)\n\n`file2' appears once at the iterator or after, so it will be added,\n_not_ merged.  Which is wrong.\n\nA naive rewrite would lead to unproperly merged files, or even files not\nhandled at all.\n\nThis changes t6060 to reproduce this case, by creating 2 files instead\nof 1, to check the correctness of the soon-to-be-rewritten merge-index.\nThe files are identical, which is not really important -- the factors\nthat could trigger this issue are that they should be separated by at\nmost one entry in the index, and that the first one in the index should\nbe trivially mergeable.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6060-merge-index.sh | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex ed449abe55..d0d6dec0c8 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -5,16 +5,19 @@ test_description='basic git merge-index / git-merge-one-file tests'\n \n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n-\tgit add file &&\n+\tcp file file2 &&\n+\tgit add file file2 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n \tsed s/10/ten/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m ten &&\n \tgit tag ten\n '\n@@ -33,8 +36,11 @@ ten\n EOF\n \n test_expect_success 'read-tree does not resolve content merge' '\n+\tcat >expect <<-\\EOF &&\n+\tfile\n+\tfile2\n+\tEOF\n \tgit read-tree -i -m base ten two &&\n-\techo file >expect &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_cmp expect unmerged\n '\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460932","messageId":"20220809185429.20098-1-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20210317204939.17890-1-alban.gruin@gmail.com","subject":"[PATCH v8 00/14] Rewrite the remaining merge strategies from shell to C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:15Z","receivedAt":"2022-08-09T19:09:34Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In an effort to reduce the number of shell scripts in git's codebase, I\npropose this patch series converting the two remaining merge strategies,\nresolve and octopus, from shell to C.  This will enable slightly better\nperformance, better integration with git itself (no more forking to\nperform these operations), better portability (Windows and shell scripts\ndon't mix well).\n\nThree scripts are actually converted: first git-merge-one-file.sh, then\ngit-merge-resolve.sh, and finally git-merge-octopus.sh.  Not only they\nare converted, but they also are modified to operate without forking,\nand then libified so they can be used by git without spawning another\nprocess.\n\nThis series keeps the commands `git merge-one-file', `git\nmerge-resolve', and `git merge-octopus', so any script depending on them\nshould keep working without changes.\n\nThis series is based on c50926e1f4 (The eleventh batch, 2022-08-08).\nThe tip is tagged as \"rewrite-merge-strategies-v8\" at\nhttps://github.com/agrn/git.\n\nChanges since v7:\n\n - The series has been rebased.\n\n - The first commit has been dropped, since t6407 was modernized by\n   8127a2b1f5 (merge tests: use \"test_must_fail\" instead of ad-hoc\n   pattern, 2022-03-07).\n\n - The `quiet' parameter of merge_entry() has been removed.  Merge\n   program failures are now reported by merge_index_path() and\n   merge_all_index().\n\n - merge_all_index() now reports merge program failures in oneshot mode,\n   as merge-index did.\n\n - In the `merge-index' builtin, the change removing the default value\n   of `merge_action' was reverted, as suggested by Johannes Schindelin.\n\n - The argument parsing and error handling in merge-one-file.c has been\n   cleaned up, as suggested by Johannes.\n\n - Parameters checking of the merge strategies were moved from the\n   builtins to merge_strategy_resolve() and merge_strategy_octopus(), as\n   suggested by Johannes.\n\n - Both strategies were modified to lock the index only once at the\n   start, and release the lock once at the end.  Calls to\n   write_index_as_tree() were replaced to a new internal function,\n   write_tree(), that do not lock the index.\n\n   In the v7, write_tree() also called lookup_tree() on the result of\n   write_index_as_tree().  As the result was only used by the octopus,\n   this call was moved to merge_strategy_octopus().\n\n   This change was suggested by Johannes.\n\n - 24ba8b70c9 (merge-resolve: abort if index does not match HEAD,\n   2022-07-23) added a check in git-merge-resolve.sh that makes the\n   strategy exit if there is changes in the worktree.  This change was\n   brought along.  Since the same check was made in merge-octopus, it\n   has been factored as a function in merge-strategies.c:\n   check_index_is_head().  merge_strategy_octopus() was modified to use\n   this new function, too.\n\n - In merge_strategies.c, fast_forward() was renamed to merge_trees().\n\n - Fixed the parameters to a call to merge_all_index() in octopus_do_merge().\n\n - The changes to merge_strategy_octopus() suggested by Johannes [0] were\n   applied.\n\n - Some commit messages were clarified.\n\n[0] https://lore.kernel.org/git/nycvar.QRO.7.76.6.2103232323330.50@tvgsbejvaqbjf.bet/\n\nAlban Gruin (14):\n  t6060: modify multiple files to expose a possible issue with\n    merge-index\n  t6060: add tests for removed files\n  merge-index: libify merge_one_path() and merge_all()\n  merge-index: drop the index\n  merge-index: add a new way to invoke `git-merge-one-file'\n  update-index: move add_cacheinfo() to read-cache.c\n  merge-one-file: rewrite in C\n  merge-resolve: rewrite in C\n  merge-recursive: move better_branch_name() to merge.c\n  merge-octopus: rewrite in C\n  merge: use the \"resolve\" strategy without forking\n  merge: use the \"octopus\" strategy without forking\n  sequencer: use the \"resolve\" strategy without forking\n  sequencer: use the \"octopus\" strategy without forking\n\n Documentation/git-merge-index.txt |   7 +-\n Makefile                          |   7 +-\n builtin.h                         |   3 +\n builtin/merge-index.c             | 122 +++---\n builtin/merge-octopus.c           |  63 ++++\n builtin/merge-one-file.c          |  92 +++++\n builtin/merge-recursive.c         |  16 +-\n builtin/merge-resolve.c           |  63 ++++\n builtin/merge.c                   |   7 +\n builtin/update-index.c            |  25 +-\n cache.h                           |  10 +-\n git-merge-octopus.sh              | 112 ------\n git-merge-one-file.sh             | 167 ---------\n git-merge-resolve.sh              |  64 ----\n git.c                             |   3 +\n merge-strategies.c                | 590 ++++++++++++++++++++++++++++++\n merge-strategies.h                |  39 ++\n merge.c                           |  12 +\n read-cache.c                      |  35 ++\n sequencer.c                       |  17 +-\n t/t6060-merge-index.sh            |  23 +-\n t/t6415-merge-dir-to-symlink.sh   |   2 +-\n t/t7607-merge-state.sh            |   2 +-\n 23 files changed, 1022 insertions(+), 459 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n create mode 100644 builtin/merge-one-file.c\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-octopus.sh\n delete mode 100755 git-merge-one-file.sh\n delete mode 100755 git-merge-resolve.sh\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v7:\n 1:  dfe230bfce <  -:  ---------- t6407: modernise tests\n 2:  575e24685d !  1:  2e23e45435 t6060: modify multiple files to expose a possible issue with merge-index\n    @@ Commit message\n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## t/t6060-merge-index.sh ##\n    -@@ t/t6060-merge-index.sh: test_expect_success 'setup diverging branches' '\n    - \tfor i in 1 2 3 4 5 6 7 8 9 10; do\n    - \t\techo $i\n    - \tdone >file &&\n    +@@ t/t6060-merge-index.sh: test_description='basic git merge-index / git-merge-one-file tests'\n    + \n    + test_expect_success 'setup diverging branches' '\n    + \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n     -\tgit add file &&\n     +\tcp file file2 &&\n     +\tgit add file file2 &&\n 3:  4f366ff363 !  2:  f48f2f7c3c t6060: add tests for removed files\n    @@ Metadata\n      ## Commit message ##\n         t6060: add tests for removed files\n     \n    -    Until now, t6060 did not not check git-mere-one-file's behaviour when a\n    +    Until now, t6060 did not not check git-merge-one-file's behaviour when a\n         file is deleted in a branch.  To avoid regressions on this during the\n    -    conversion, this adds a new file, `file3', in the commit tagged as`base', and\n    -    deletes it in the commit tagged as `two'.\n    +    conversion from shell to C, this adds a new file, `file3', in the commit\n    +    tagged as `base', and deletes it in the commit tagged as `two'.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## t/t6060-merge-index.sh ##\n    -@@ t/t6060-merge-index.sh: test_expect_success 'setup diverging branches' '\n    - \t\techo $i\n    - \tdone >file &&\n    +@@ t/t6060-merge-index.sh: test_description='basic git merge-index / git-merge-one-file tests'\n    + test_expect_success 'setup diverging branches' '\n    + \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n      \tcp file file2 &&\n     -\tgit add file file2 &&\n     +\tcp file file3 &&\n 4:  6af79a6b2d !  3:  331141f0cb merge-index: libify merge_one_path() and merge_all()\n    @@ Makefile: LIB_OBJS += merge-blobs.o\n      LIB_OBJS += merge-recursive.o\n     +LIB_OBJS += merge-strategies.o\n      LIB_OBJS += merge.o\n    - LIB_OBJS += mergesort.o\n      LIB_OBJS += midx.o\n    + LIB_OBJS += name-hash.o\n     \n      ## builtin/merge-index.c ##\n     @@\n    @@ builtin/merge-index.c\n     -static void merge_all(void)\n     -{\n     -\tint i;\n    +-\t/* TODO: audit for interaction with sparse-index. */\n    +-\tensure_full_index(&the_index);\n     -\tfor (i = 0; i < active_nr; i++) {\n     -\t\tconst struct cache_entry *ce = active_cache[i];\n     -\t\tif (!ce_stage(ce))\n    @@ merge-strategies.c (new)\n     +#include \"cache.h\"\n     +#include \"merge-strategies.h\"\n     +\n    -+static int merge_entry(struct index_state *istate, int quiet, unsigned int pos,\n    ++static int merge_entry(struct index_state *istate, unsigned int pos,\n     +\t\t       const char *path, int *err, merge_fn fn, void *data)\n     +{\n     +\tint found = 0;\n    @@ merge-strategies.c (new)\n     +\t\treturn error(_(\"%s is not in the cache\"), path);\n     +\n     +\tif (fn(istate, oids[0], oids[1], oids[2], path,\n    -+\t       modes[0], modes[1], modes[2], data)) {\n    -+\t\tif (!quiet)\n    -+\t\t\terror(_(\"Merge program failed\"));\n    ++\t       modes[0], modes[1], modes[2], data))\n     +\t\t(*err)++;\n    -+\t}\n     +\n     +\treturn found;\n     +}\n    @@ merge-strategies.c (new)\n     +\t * already merged and there is nothing to do.\n     +\t */\n     +\tif (pos < 0) {\n    -+\t\tret = merge_entry(istate, quiet || oneshot, -pos - 1, path, &err, fn, data);\n    ++\t\tret = merge_entry(istate, -pos - 1, path, &err, fn, data);\n     +\t\tif (ret == -1)\n     +\t\t\treturn -1;\n    -+\t\telse if (err)\n    ++\t\telse if (err) {\n    ++\t\t\tif (!quiet && !oneshot)\n    ++\t\t\t\terror(_(\"merge program failed\"));\n     +\t\t\treturn 1;\n    ++\t\t}\n     +\t}\n     +\treturn 0;\n     +}\n    @@ merge-strategies.c (new)\n     +\tint err = 0, ret;\n     +\tunsigned int i;\n     +\n    ++\t/* TODO: audit for interaction with sparse-index. */\n    ++\tensure_full_index(istate);\n     +\tfor (i = 0; i < istate->cache_nr; i++) {\n     +\t\tconst struct cache_entry *ce = istate->cache[i];\n     +\t\tif (!ce_stage(ce))\n     +\t\t\tcontinue;\n     +\n    -+\t\tret = merge_entry(istate, quiet || oneshot, i, ce->name, &err, fn, data);\n    ++\t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n     +\t\tif (ret > 0)\n     +\t\t\ti += ret - 1;\n     +\t\telse if (ret == -1)\n     +\t\t\treturn -1;\n     +\n    -+\t\tif (err && !oneshot)\n    ++\t\tif (err && !oneshot) {\n    ++\t\t\tif (!quiet)\n    ++\t\t\t\terror(_(\"merge program failed\"));\n     +\t\t\treturn 1;\n    ++\t\t}\n     +\t}\n     +\n    ++\tif (err && !quiet)\n    ++\t\terror(_(\"merge program failed\"));\n     +\treturn err;\n     +}\n     \n    @@ merge-strategies.h (new)\n     +\t\t    merge_fn fn, void *data);\n     +\n     +#endif /* MERGE_STRATEGIES_H */\n    +\n    + ## t/t7607-merge-state.sh ##\n    +@@ t/t7607-merge-state.sh: test_expect_success 'Ensure we restore original state if no merge strategy handl\n    + \t# just hit conflicts, it completely fails and says that it cannot\n    + \t# handle this type of merge.\n    + \ttest_expect_code 2 git merge branch2 branch3 >output 2>&1 &&\n    +-\tgrep \"fatal: merge program failed\" output &&\n    ++\tgrep \"error: merge program failed\" output &&\n    + \tgrep \"Should not be doing an octopus\" output &&\n    + \n    + \t# Make sure we did not leave stray changes around when no appropriate\n 5:  909ed66114 !  4:  a3c0815fc1 merge-index: drop the index\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n     +\tif (repo_read_index(r) < 0)\n     +\t\tdie(\"invalid index\");\n      \n    + \t/* TODO: audit for interaction with sparse-index. */\n    +-\tensure_full_index(&the_index);\n    ++\tensure_full_index(r->index);\n    + \n      \ti = 1;\n      \tif (!strcmp(argv[i], \"-o\")) {\n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n 6:  1a8aba05bd !  5:  558e65e39b merge-index: add a new way to invoke `git-merge-one-file'\n    @@ Documentation/git-merge-index.txt: git-merge-index - Run a merge for files needi\n      SYNOPSIS\n      --------\n      [verse]\n    --'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*)\n    -+'git merge-index' [-o] [-q] (<merge-program> | --use=merge-one-file) (-a | [--] <file>*)\n    +-'git merge-index' [-o] [-q] <merge-program> (-a | ( [--] <file>...) )\n    ++'git merge-index' [-o] [-q] (<merge-program> | --use=merge-one-file) (-a | ( [--] <file>...) )\n      \n      DESCRIPTION\n      -----------\n 7:  1f6635512c !  6:  94edebfb69 update-index: move add_cacheinfo() to read-cache.c\n    @@ Commit message\n         This moves the function add_cacheinfo() that already exists in\n         update-index.c to update-index.c, renames it add_to_index_cacheinfo(),\n         and adds an `istate' parameter.  The new cache entry is returned through\n    -    a pointer passed in the parameters.  The return value is either 0\n    -    (success), -1 (invalid path), or -2 (failed to add the file in the\n    -    index).\n    +    a pointer passed in the parameters.  This function can return three\n    +    values:\n    +\n    +     - 0, when the file has been successfully added to the index;\n    +     - ADD_TO_INDEX_CACHEINFO_INVALID_PATH, when the file does not exists;\n    +     - ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD, when the file could not be\n    +       added to the index.\n     \n         This will become useful in the next commit, when the three-way merge\n         will need to call this function.\n 8:  8755608f6d !  7:  123d299df7 merge-one-file: rewrite in C\n    @@ builtin.h: int cmd_merge_base(int argc, const char **argv, const char *prefix);\n      int cmd_mktag(int argc, const char **argv, const char *prefix);\n     \n      ## builtin/merge-index.c ##\n    -@@ builtin/merge-index.c: static int merge_one_file_spawn(struct index_state *istate,\n    - int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    - {\n    - \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n    --\tmerge_fn merge_action = merge_one_file_spawn;\n    -+\tmerge_fn merge_action;\n    - \tstruct lock_file lock = LOCK_INIT;\n    - \tstruct repository *r = the_repository;\n    - \tconst char *use_internal = NULL;\n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n      \n      \tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n     +\t\t\tmerge_action = merge_one_file_func;\n      \t\telse\n      \t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n    --\t}\n     +\n     +\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t} else\n    -+\t\tmerge_action = merge_one_file_spawn;\n    + \t}\n      \n      \tfor (; i < argc; i++) {\n    - \t\tconst char *arg = argv[i];\n     \n      ## builtin/merge-one-file.c (new) ##\n     @@\n    @@ builtin/merge-one-file.c (new)\n     +\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n     +\t\"Blob ids and modes should be empty for missing files.\";\n     +\n    -+static int read_mode(const char *name, const char *arg, unsigned int *mode)\n    ++static int read_param(const char *name, const char *arg_blob, const char *arg_mode,\n    ++\t\t      struct object_id *blob, struct object_id **p_blob, unsigned int *mode)\n     +{\n    -+\tchar *last;\n    -+\tint ret = 0;\n    ++\tif (*arg_blob && !get_oid_hex(arg_blob, blob)) {\n    ++\t\tchar *last;\n     +\n    -+\t*mode = strtol(arg, &last, 8);\n    ++\t\t*p_blob = blob;\n    ++\t\t*mode = strtol(arg_mode, &last, 8);\n     +\n    -+\tif (*last)\n    -+\t\tret = error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n    -+\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n    -+\t\tret = error(_(\"invalid '%s' mode: %o\"), name, *mode);\n    ++\t\tif (*last)\n    ++\t\t\treturn error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n    ++\t\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n    ++\t\t\treturn error(_(\"invalid '%s' mode: %o\"), name, *mode);\n    ++\t} else if (!*arg_blob && *arg_mode)\n    ++\t\treturn error(_(\"no '%s' object id given, but a mode was still given.\"), name);\n     +\n    -+\treturn ret;\n    ++\treturn 0;\n     +}\n     +\n     +int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n    @@ builtin/merge-one-file.c (new)\n     +\n     +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n     +\n    -+\tif (!get_oid_hex(argv[1], &orig_blob)) {\n    -+\t\tp_orig_blob = &orig_blob;\n    -+\t\tret = read_mode(\"orig\", argv[5], &orig_mode);\n    -+\t} else if (!*argv[1] && *argv[5])\n    -+\t\tret = error(_(\"no 'orig' object id given, but a mode was still given.\"));\n    ++\tif (read_param(\"orig\", argv[1], argv[5], &orig_blob,\n    ++\t\t       &p_orig_blob, &orig_mode))\n    ++\t\tret = -1;\n     +\n    -+\tif (!get_oid_hex(argv[2], &our_blob)) {\n    -+\t\tp_our_blob = &our_blob;\n    -+\t\tret = read_mode(\"our\", argv[6], &our_mode);\n    -+\t} else if (!*argv[2] && *argv[6])\n    -+\t\tret = error(_(\"no 'our' object id given, but a mode was still given.\"));\n    ++\tif (read_param(\"our\", argv[2], argv[6], &our_blob,\n    ++\t\t       &p_our_blob, &our_mode))\n    ++\t\tret = -1;\n     +\n    -+\tif (!get_oid_hex(argv[3], &their_blob)) {\n    -+\t\tp_their_blob = &their_blob;\n    -+\t\tret = read_mode(\"their\", argv[7], &their_mode);\n    -+\t} else if (!*argv[3] && *argv[7])\n    -+\t\tret = error(_(\"no 'their' object id given, but a mode was still given.\"));\n    ++\tif (read_param(\"their\", argv[3], argv[7], &their_blob,\n    ++\t\t       &p_their_blob, &their_mode))\n    ++\t\tret = -1;\n     +\n     +\tif (ret)\n     +\t\treturn ret;\n    @@ merge-strategies.c\n     @@\n      #include \"cache.h\"\n     +#include \"dir.h\"\n    ++#include \"entry.h\"\n      #include \"merge-strategies.h\"\n     +#include \"xdiff-interface.h\"\n     +\n    @@ merge-strategies.c\n     +\t\tread_mmblob(mmfs + 0, orig_blob);\n     +\t} else {\n     +\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n    -+\t\tread_mmblob(mmfs + 0, &null_oid);\n    ++\t\tread_mmblob(mmfs + 0, null_oid());\n     +\t}\n     +\n     +\tread_mmblob(mmfs + 1, our_blob);\n    @@ merge-strategies.c\n     +\t\t\t       orig_mode, our_mode, their_mode);\n     +}\n      \n    - static int merge_entry(struct index_state *istate, int quiet, unsigned int pos,\n    + static int merge_entry(struct index_state *istate, unsigned int pos,\n      \t\t       const char *path, int *err, merge_fn fn, void *data)\n     @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n      \t\t    merge_fn fn, void *data)\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     -\tunsigned int i;\n     +\tunsigned int i, prev_nr;\n      \n    - \tfor (i = 0; i < istate->cache_nr; i++) {\n    - \t\tconst struct cache_entry *ce = istate->cache[i];\n    + \t/* TODO: audit for interaction with sparse-index. */\n    + \tensure_full_index(istate);\n    +@@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n      \t\tif (!ce_stage(ce))\n      \t\t\tcontinue;\n      \n     +\t\tprev_nr = istate->cache_nr;\n    - \t\tret = merge_entry(istate, quiet || oneshot, i, ce->name, &err, fn, data);\n    + \t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n     -\t\tif (ret > 0)\n     -\t\t\ti += ret - 1;\n     -\t\telse if (ret == -1)\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     +\t\t} else if (ret == -1)\n      \t\t\treturn -1;\n      \n    - \t\tif (err && !oneshot)\n    + \t\tif (err && !oneshot) {\n     \n      ## merge-strategies.h ##\n     @@\n 9:  3ecf49a8ac !  8:  f181cef10b merge-resolve: rewrite in C\n    @@ Commit message\n            all the setup needed).\n     \n          - The call to `write-tree' is replaced by a call to\n    -       write_index_as_tree().\n    +       cache_tree_update().  This call is wrapped in a new function,\n    +       write_tree().  It is made to mimick write_index_as_tree() with\n    +       WRITE_TREE_SILENT flag, but without locking the index; this is taken\n    +       care directly in merge_strategies_resolve().\n    +\n    +     - The call to `diff-index ...' is replaced by a call to\n    +       repo_index_has_changes().\n     \n          - The call to `merge-index', needed to invoke `git merge-one-file', is\n            replaced by a call to the new merge_all_index() function.\n     \n         The index is read in cmd_merge_resolve(), and is wrote back by\n    -    merge_strategies_resolve().\n    +    merge_strategies_resolve().  This is to accomodate future applications:\n    +    in `git-merge', the index has already been read when the merge strategy\n    +    is called, so it would be redundant to read it again when the builtin\n    +    will be able to use merge_strategies_resolve() directly.\n     \n         The parameters of merge_strategies_resolve() will be surprising at first\n         glance: why using a commit list for `bases' and `remote', where we could\n    @@ Commit message\n         frictions later, merge_strategies_resolve() takes the same types of\n         parameters.\n     \n    +    merge_strategies_resolve() locks the index only once, at the beginning\n    +    of the merge, and releases it when the merge has been completed.\n    +\n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## Makefile ##\n    @@ builtin/merge-resolve.c (new)\n     +\t\t}\n     +\t}\n     +\n    -+\t/*\n    -+\t * Give up if we are given two or more remotes.  Not handling\n    -+\t * octopus.\n    -+\t */\n    -+\tif (remote && remote->next)\n    -+\t\treturn 2;\n    -+\n    -+\t/* Give up if this is a baseless merge. */\n    -+\tif (!bases)\n    -+\t\treturn 2;\n    -+\n     +\treturn merge_strategies_resolve(r, bases, head, remote);\n     +}\n     \n    @@ git-merge-resolve.sh (deleted)\n     -#\n     -# Resolve two trees, using enhanced multi-base read-tree.\n     -\n    +-. git-sh-setup\n    +-\n    +-# Abort if index does not match HEAD\n    +-if ! git diff-index --quiet --cached HEAD --\n    +-then\n    +-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n    +-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n    +-    exit 2\n    +-fi\n    +-\n     -# The first parameters up to -- are merge bases; the rest are heads.\n     -bases= head= remotes= sep_seen=\n     -for arg\n    @@ git.c: static struct cmd_struct commands[] = {\n      \t{ \"merge-recursive-theirs\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n     +\t{ \"merge-resolve\", cmd_merge_resolve, 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     \n      ## merge-strategies.c ##\n    @@ merge-strategies.c\n      #include \"cache.h\"\n     +#include \"cache-tree.h\"\n      #include \"dir.h\"\n    + #include \"entry.h\"\n     +#include \"lockfile.h\"\n      #include \"merge-strategies.h\"\n     +#include \"unpack-trees.h\"\n      #include \"xdiff-interface.h\"\n      \n    ++static int check_index_is_head(struct repository *r, const char *head_arg)\n    ++{\n    ++\tstruct commit *head_commit;\n    ++\tstruct tree *head_tree;\n    ++\tstruct object_id head;\n    ++\tstruct strbuf sb = STRBUF_INIT;\n    ++\n    ++\tget_oid(head_arg, &head);\n    ++\thead_commit = lookup_commit_reference(r, &head);\n    ++\thead_tree = repo_get_commit_tree(r, head_commit);\n    ++\n    ++\tif (repo_index_has_changes(r, head_tree, &sb)) {\n    ++\t\terror(_(\"Your local changes to the following files \"\n    ++\t\t\t\"would be overwritten by merge:\\n  %s\"),\n    ++\t\t      sb.buf);\n    ++\t\tstrbuf_release(&sb);\n    ++\t\treturn 1;\n    ++\t}\n    ++\n    ++\treturn 0;\n    ++}\n    ++\n      static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n    + \t\t\t\t     const struct object_id *oid, const char *path,\n    + \t\t\t\t     int checkout)\n     @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    - \n    + \t\terror(_(\"merge program failed\"));\n      \treturn err;\n      }\n     +\n    -+static int fast_forward(struct repository *r, struct tree_desc *t,\n    -+\t\t\tint nr, int aggressive)\n    ++static int merge_trees(struct repository *r, struct tree_desc *t,\n    ++\t\t       int nr, int aggressive)\n     +{\n     +\tstruct unpack_trees_options opts;\n    -+\tstruct lock_file lock = LOCK_INIT;\n     +\n     +\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n    -+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n     +\n     +\tmemset(&opts, 0, sizeof(opts));\n     +\topts.head_idx = 1;\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     +\n     +\tif (unpack_trees(nr, t, &opts))\n     +\t\treturn -1;\n    -+\n    -+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n    -+\t\treturn error(_(\"unable to write new index file\"));\n    -+\n     +\treturn 0;\n     +}\n     +\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     +\treturn 0;\n     +}\n     +\n    ++static int write_tree(struct repository *r)\n    ++{\n    ++\tint was_valid;\n    ++\twas_valid = r->index->cache_tree &&\n    ++\t\tcache_tree_fully_valid(r->index->cache_tree);\n    ++\n    ++\tif (!was_valid && cache_tree_update(r->index, WRITE_TREE_SILENT) < 0)\n    ++\t\treturn WRITE_TREE_UNMERGED_INDEX;\n    ++\treturn 0;\n    ++}\n    ++\n     +int merge_strategies_resolve(struct repository *r,\n     +\t\t\t     struct commit_list *bases, const char *head_arg,\n     +\t\t\t     struct commit_list *remote)\n     +{\n     +\tstruct tree_desc t[MAX_UNPACK_TREES];\n    -+\tstruct object_id head, oid;\n     +\tstruct commit_list *i;\n    -+\tint nr = 0;\n    ++\tstruct lock_file lock = LOCK_INIT;\n    ++\tint nr = 0, ret = 0;\n     +\n    -+\tif (head_arg)\n    -+\t\tget_oid(head_arg, &head);\n    ++\t/* Abort if index does not match head */\n    ++\tif (check_index_is_head(r, head_arg))\n    ++\t\treturn 2;\n    ++\n    ++\t/*\n    ++\t * Give up if we are given two or more remotes.  Not handling\n    ++\t * octopus.\n    ++\t */\n    ++\tif (remote && remote->next)\n    ++\t\treturn 2;\n    ++\n    ++\t/* Give up if this is a baseless merge. */\n    ++\tif (!bases)\n    ++\t\treturn 2;\n     +\n     +\tputs(_(\"Trying simple merge.\"));\n     +\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     +\t}\n     +\n     +\tif (head_arg) {\n    -+\t\tstruct tree *tree = parse_tree_indirect(&head);\n    ++\t\tstruct object_id head;\n    ++\t\tstruct tree *tree;\n    ++\n    ++\t\tget_oid(head_arg, &head);\n    ++\t\ttree = parse_tree_indirect(&head);\n    ++\n     +\t\tif (add_tree(tree, t + (nr++)))\n     +\t\t\treturn 2;\n     +\t}\n    @@ merge-strategies.c: int merge_all_index(struct index_state *istate, int oneshot,\n     +\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n     +\t\treturn 2;\n     +\n    -+\tif (fast_forward(r, t, nr, 1))\n    ++\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    ++\n    ++\tif (merge_trees(r, t, nr, 1)) {\n    ++\t\trollback_lock_file(&lock);\n     +\t\treturn 2;\n    ++\t}\n     +\n    -+\tif (write_index_as_tree(&oid, r->index, r->index_file,\n    -+\t\t\t\tWRITE_TREE_SILENT, NULL)) {\n    -+\t\tint ret;\n    -+\t\tstruct lock_file lock = LOCK_INIT;\n    -+\n    ++\tif (write_tree(r)) {\n     +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n    -+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n     +\t\tret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n    -+\n    -+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n    -+\t\treturn !!ret;\n     +\t}\n     +\n    -+\treturn 0;\n    ++\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n    ++\t\treturn !!error(_(\"unable to write new index file\"));\n    ++\treturn !!ret;\n     +}\n     \n      ## merge-strategies.h ##\n10:  615b04d417 =  9:  cc1ba1acc9 merge-recursive: move better_branch_name() to merge.c\n11:  a6ece04f3d ! 10:  c48e2de914 merge-octopus: rewrite in C\n    @@ Commit message\n          - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n            unpack_trees().\n     \n    -     - The call to `write-tree' is replaced by a call to\n    -       write_index_as_tree().\n    +     - The call to `write-tree' is replaced by a call to write_tree().\n     \n          - The call to `diff-index ...' is replaced by a call to\n            repo_index_has_changes().\n    @@ Commit message\n          - The call to `merge-index', needed to invoke `git merge-one-file', is\n            replaced by a call to merge_all_index().\n     \n    -    The index is read in cmd_merge_octopus(), and is wrote back by\n    -    merge_strategies_octopus().\n    +    The index is read in cmd_merge_octopus(), and is written back by\n    +    merge_strategies_octopus(), for the same reason as merge-resolve.\n     \n    -    Here to, merge_strategies_octopus() takes two commit lists and a string\n    -    to reduce frictions when try_merge_strategies() will be modified to call\n    -    it directly.\n    +    Here too, merge_strategies_octopus() takes two commit lists and a string\n    +    to reduce friction when try_merge_strategies() will be modified to call\n    +    it directly.  It also locks the index at the start of the merge, and\n    +    releases it at the end.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n    @@ builtin/merge-octopus.c (new)\n     +\t\t}\n     +\t}\n     +\n    -+\t/*\n    -+\t * Reject if this is not an octopus -- resolve should be used\n    -+\t * instead.\n    -+\t */\n    -+\tif (commit_list_count(remotes) < 2)\n    -+\t\treturn 2;\n    -+\n     +\treturn merge_strategies_octopus(r, bases, head_arg, remotes);\n     +}\n     \n    @@ merge-strategies.c\n      #include \"cache-tree.h\"\n     +#include \"commit-reach.h\"\n      #include \"dir.h\"\n    + #include \"entry.h\"\n      #include \"lockfile.h\"\n    - #include \"merge-strategies.h\"\n     @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n    - \n    - \treturn 0;\n    + \t\treturn !!error(_(\"unable to write new index file\"));\n    + \treturn !!ret;\n      }\n     +\n    -+static int write_tree(struct repository *r, struct tree **reference_tree)\n    -+{\n    -+\tstruct object_id oid;\n    -+\tint ret;\n    -+\n    -+\tif (!(ret = write_index_as_tree(&oid, r->index, r->index_file,\n    -+\t\t\t\t\tWRITE_TREE_SILENT, NULL)))\n    -+\t\t*reference_tree = lookup_tree(r, &oid);\n    -+\n    -+\treturn ret;\n    -+}\n    -+\n     +static int octopus_fast_forward(struct repository *r, const char *branch_name,\n    -+\t\t\t\tstruct tree *tree_head, struct tree *current_tree,\n    -+\t\t\t\tstruct tree **reference_tree)\n    ++\t\t\t\tstruct tree *tree_head, struct tree *current_tree)\n     +{\n     +\t/*\n     +\t * The first head being merged was a fast-forward.  Advance the\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n     +\tif (add_tree(current_tree, t + 1))\n     +\t\treturn -1;\n    -+\tif (fast_forward(r, t, 2, 0))\n    ++\tif (merge_trees(r, t, 2, 0))\n     +\t\treturn -1;\n    -+\tif (write_tree(r, reference_tree))\n    ++\tif (write_tree(r))\n     +\t\treturn -1;\n     +\n     +\treturn 0;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\n     +static int octopus_do_merge(struct repository *r, const char *branch_name,\n     +\t\t\t    struct commit_list *common, struct tree *current_tree,\n    -+\t\t\t    struct tree **reference_tree)\n    ++\t\t\t    struct tree *reference_tree)\n     +{\n     +\tstruct tree_desc t[MAX_UNPACK_TREES];\n     +\tstruct commit_list *i;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t\treturn -1;\n     +\t}\n     +\n    -+\tif (add_tree(*reference_tree, t + (nr++)))\n    ++\tif (add_tree(reference_tree, t + (nr++)))\n     +\t\treturn -1;\n     +\tif (add_tree(current_tree, t + (nr++)))\n     +\t\treturn -1;\n    -+\tif (fast_forward(r, t, nr, 1))\n    ++\tif (merge_trees(r, t, nr, 1))\n     +\t\treturn 2;\n     +\n    -+\tif (write_tree(r, reference_tree)) {\n    -+\t\tstruct lock_file lock = LOCK_INIT;\n    -+\n    ++\tif (write_tree(r)) {\n     +\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n    -+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    -+\t\tret = !!merge_all_index(r->index, 0, 0, merge_one_file_func, NULL);\n    -+\t\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n    -+\n    -+\t\twrite_tree(r, reference_tree);\n    ++\t\tret = !!merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n    ++\t\twrite_tree(r);\n     +\t}\n     +\n     +\treturn ret;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\tstruct tree *reference_tree, *head_tree;\n     +\tstruct commit_list *i;\n     +\tstruct object_id head;\n    -+\tstruct strbuf sb = STRBUF_INIT;\n    ++\tstruct lock_file lock = LOCK_INIT;\n    ++\n    ++\t/*\n    ++\t * Reject if this is not an octopus -- resolve should be used\n    ++\t * instead.\n    ++\t */\n    ++\tif (commit_list_count(remotes) < 2)\n    ++\t\treturn 2;\n    ++\n    ++\t/* Abort if index does not match head */\n    ++\tif (check_index_is_head(r, head_arg))\n    ++\t\treturn 2;\n     +\n     +\tget_oid(head_arg, &head);\n     +\thead_commit = lookup_commit_reference(r, &head);\n     +\thead_tree = repo_get_commit_tree(r, head_commit);\n     +\n    -+\tif (parse_tree(head_tree))\n    -+\t\treturn 2;\n    -+\n    -+\tif (repo_index_has_changes(r, head_tree, &sb)) {\n    -+\t\terror(_(\"Your local changes to the following files \"\n    -+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n    -+\t\t      sb.buf);\n    -+\t\tstrbuf_release(&sb);\n    -+\t\treturn 2;\n    -+\t}\n    -+\n     +\tCALLOC_ARRAY(reference_commits, commit_list_count(remotes) + 1);\n     +\treference_commits[0] = head_commit;\n     +\treference_tree = head_tree;\n     +\n    ++\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n    ++\n     +\tfor (i = remotes; i && i->item; i = i->next) {\n     +\t\tstruct commit *c = i->item;\n     +\t\tstruct object_id *oid = &c->object.oid;\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\n     +\t\t\tfree(branch_name);\n     +\t\t\tfree_commit_list(common);\n    -+\t\t\tfree(reference_commits);\n     +\n    -+\t\t\treturn 2;\n    ++\t\t\tret = 2;\n    ++\t\t\tbreak;\n     +\t\t}\n     +\n    -+\t\tfor (j = common; j && !up_to_date && ff_merge; j = j->next) {\n    -+\t\t\tup_to_date |= oideq(&j->item->object.oid, oid);\n    -+\n    -+\t\t\tif (!j->next &&\n    -+\t\t\t    !oideq(&j->item->object.oid,\n    -+\t\t\t\t   &reference_commits[nr_references - 1]->object.oid))\n    -+\t\t\t\tff_merge = 0;\n    ++\t\t/*\n    ++\t\t * If `oid' is reachable from `HEAD', we're already up\n    ++\t\t * to date.\n    ++\t\t */\n    ++\t\tfor (j = common; j; j = j->next) {\n    ++\t\t\tif (oideq(&j->item->object.oid, oid)) {\n    ++\t\t\t\tup_to_date = 1;\n    ++\t\t\t\tbreak;\n    ++\t\t\t}\n     +\t\t}\n     +\n     +\t\tif (up_to_date) {\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t\tcontinue;\n     +\t\t}\n     +\n    -+\t\tif (ff_merge) {\n    -+\t\t\tret = octopus_fast_forward(r, branch_name, head_tree,\n    -+\t\t\t\t\t\t   current_tree, &reference_tree);\n    ++\t\t/*\n    ++\t\t * If we could fast-forward so far and `HEAD' is the\n    ++\t\t * single merge base with the current `remote' revision,\n    ++\t\t * keep fast-forwarding.\n    ++\t\t */\n    ++\t\tif (ff_merge && common && !common->next && nr_references == 1 &&\n    ++\t\t    oideq(&common->item->object.oid,\n    ++\t\t\t  &reference_commits[0]->object.oid)) {\n    ++\t\t\tret = octopus_fast_forward(r, branch_name, head_tree, current_tree);\n     +\t\t\tnr_references = 0;\n     +\t\t} else {\n     +\t\t\tret = octopus_do_merge(r, branch_name, common,\n    -+\t\t\t\t\t       current_tree, &reference_tree);\n    ++\t\t\t\t\t       current_tree, reference_tree);\n    ++\t\t\tff_merge = 0;\n     +\t\t}\n     +\n     +\t\tfree(branch_name);\n    @@ merge-strategies.c: int merge_strategies_resolve(struct repository *r,\n     +\t\t\tputs(_(\"Automated merge did not work.\"));\n     +\t\t\tputs(_(\"Should not be doing an octopus.\"));\n     +\n    -+\t\t\tfree(reference_commits);\n    -+\t\t\treturn 2;\n    ++\t\t\tret = 2;\n    ++\t\t\tbreak;\n     +\t\t}\n     +\n     +\t\treference_commits[nr_references++] = c;\n    ++\t\treference_tree = lookup_tree(r, &r->index->cache_tree->oid);\n     +\t}\n     +\n     +\tfree(reference_commits);\n    ++\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n    ++\n     +\treturn ret;\n     +}\n     \n12:  cc1500147b = 11:  bcc7b851ef merge: use the \"resolve\" strategy without forking\n13:  ec3dc3b81e = 12:  9ba13186ed merge: use the \"octopus\" strategy without forking\n14:  e7dc4a15d4 ! 13:  a815a16f33 sequencer: use the \"resolve\" strategy without forking\n    @@ Commit message\n     \n      ## sequencer.c ##\n     @@\n    - #include \"commit-reach.h\"\n    - #include \"rebase-interactive.h\"\n      #include \"reset.h\"\n    + #include \"branch.h\"\n    + #include \"log-tree.h\"\n     +#include \"merge-strategies.h\"\n      \n      #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n15:  34280dd82d ! 14:  5a11fd0e71 sequencer: use the \"octopus\" merge strategy without forking\n    @@ Metadata\n     Author: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## Commit message ##\n    -    sequencer: use the \"octopus\" merge strategy without forking\n    +    sequencer: use the \"octopus\" strategy without forking\n     \n         This teaches the sequencer to invoke the \"octopus\" strategy with a\n         function call instead of forking.\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460933","messageId":"20220809185429.20098-3-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 02/14] t6060: add tests for removed files","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:17Z","receivedAt":"2022-08-09T19:09:37Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Until now, t6060 did not not check git-merge-one-file's behaviour when a\nfile is deleted in a branch.  To avoid regressions on this during the\nconversion from shell to C, this adds a new file, `file3', in the commit\ntagged as `base', and deletes it in the commit tagged as `two'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t6060-merge-index.sh | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex d0d6dec0c8..bb4da4bbb2 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -6,12 +6,14 @@ test_description='basic git merge-index / git-merge-one-file tests'\n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n \tcp file file2 &&\n-\tgit add file file2 &&\n+\tcp file file3 &&\n+\tgit add file file2 file3 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n \tcp file file2 &&\n+\tgit rm file3 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n@@ -39,6 +41,7 @@ test_expect_success 'read-tree does not resolve content merge' '\n \tcat >expect <<-\\EOF &&\n \tfile\n \tfile2\n+\tfile3\n \tEOF\n \tgit read-tree -i -m base ten two &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460934","messageId":"20220809185429.20098-4-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 03/14] merge-index: libify merge_one_path() and merge_all()","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:18Z","receivedAt":"2022-08-09T19:09:40Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The \"resolve\" and \"octopus\" merge strategies do not call directly `git\nmerge-one-file', they delegate the work to another git command, `git\nmerge-index', that will loop over files in the index and call the\nspecified command.  Unfortunately, these functions are not part of\nlibgit.a, which means that once rewritten, the strategies would still\nhave to invoke `merge-one-file' by spawning a new process first.\n\nTo avoid this, this moves and renames merge_one_path(), merge_all(), and\ntheir helpers to merge-strategies.c.  They also take a callback to\ndictate what they should do for each file.  For now, to preserve the\nbehaviour of `merge-index', only one callback, launching a new process,\nis defined.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile               |  1 +\n builtin/merge-index.c  | 92 ++++++++++++++----------------------------\n merge-strategies.c     | 82 +++++++++++++++++++++++++++++++++++++\n merge-strategies.h     | 18 +++++++++\n t/t7607-merge-state.sh |  2 +-\n 5 files changed, 133 insertions(+), 62 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex 2ec9b2dc6b..40d1be4e5e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -991,6 +991,7 @@ LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-ort-wrappers.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += midx.o\n LIB_OBJS += name-hash.o\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex c0383fe9df..f66cc515d8 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,76 +1,43 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"merge-strategies.h\"\n #include \"run-command.h\"\n \n static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n \n-static int merge_entry(int pos, const char *path)\n+static int merge_one_file_spawn(struct index_state *istate,\n+\t\t\t\tconst struct object_id *orig_blob,\n+\t\t\t\tconst struct object_id *our_blob,\n+\t\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\t\tvoid *data)\n {\n-\tint found;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n+\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n+\tchar modes[3][10] = {{0}};\n+\tconst char *arguments[] = { pgm, oids[0], oids[1], oids[2],\n+\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n \n-\tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n-\tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n-\n-\tif (run_command_v_opt(arguments, 0)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n-\t\t\texit(1);\n-\t\t}\n+\tif (orig_blob) {\n+\t\toid_to_hex_r(oids[0], orig_blob);\n+\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n \t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(const char *path)\n-{\n-\tint pos = cache_name_pos(path, strlen(path));\n \n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n-}\n+\tif (our_blob) {\n+\t\toid_to_hex_r(oids[1], our_blob);\n+\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n+\t}\n \n-static void merge_all(void)\n-{\n-\tint i;\n-\t/* TODO: audit for interaction with sparse-index. */\n-\tensure_full_index(&the_index);\n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n+\tif (their_blob) {\n+\t\toid_to_hex_r(oids[2], their_blob);\n+\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n \t}\n+\n+\treturn run_command_v_opt(arguments, 0);\n }\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -94,7 +61,9 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tquiet = 1;\n \t\ti++;\n \t}\n+\n \tpgm = argv[i++];\n+\n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tif (!force_file && *arg == '-') {\n@@ -103,14 +72,15 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n+\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n+\t\t\t\t\t\t       merge_one_file_spawn, NULL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\tmerge_one_path(arg);\n+\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n+\t\t\t\t\tmerge_one_file_spawn, NULL);\n \t}\n-\tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n+\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 0000000000..418c9dd710\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,82 @@\n+#include \"cache.h\"\n+#include \"merge-strategies.h\"\n+\n+static int merge_entry(struct index_state *istate, unsigned int pos,\n+\t\t       const char *path, int *err, merge_fn fn, void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = {NULL};\n+\tunsigned int modes[3] = {0};\n+\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\treturn error(_(\"%s is not in the cache\"), path);\n+\n+\tif (fn(istate, oids[0], oids[1], oids[2], path,\n+\t       modes[0], modes[1], modes[2], data))\n+\t\t(*err)++;\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data)\n+{\n+\tint pos = index_name_pos(istate, path, strlen(path)), ret, err = 0;\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos < 0) {\n+\t\tret = merge_entry(istate, -pos - 1, path, &err, fn, data);\n+\t\tif (ret == -1)\n+\t\t\treturn -1;\n+\t\telse if (err) {\n+\t\t\tif (!quiet && !oneshot)\n+\t\t\t\terror(_(\"merge program failed\"));\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data)\n+{\n+\tint err = 0, ret;\n+\tunsigned int i;\n+\n+\t/* TODO: audit for interaction with sparse-index. */\n+\tensure_full_index(istate);\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n+\t\tif (ret > 0)\n+\t\t\ti += ret - 1;\n+\t\telse if (ret == -1)\n+\t\t\treturn -1;\n+\n+\t\tif (err && !oneshot) {\n+\t\t\tif (!quiet)\n+\t\t\t\terror(_(\"merge program failed\"));\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\tif (err && !quiet)\n+\t\terror(_(\"merge program failed\"));\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 0000000000..88f476f170\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,18 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+#include \"object.h\"\n+\n+typedef int (*merge_fn)(struct index_state *istate,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_fn fn, void *data);\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_fn fn, void *data);\n+\n+#endif /* MERGE_STRATEGIES_H */\ndiff --git a/t/t7607-merge-state.sh b/t/t7607-merge-state.sh\nindex 89a62ac53b..96befa5b80 100755\n--- a/t/t7607-merge-state.sh\n+++ b/t/t7607-merge-state.sh\n@@ -20,7 +20,7 @@ test_expect_success 'Ensure we restore original state if no merge strategy handl\n \t# just hit conflicts, it completely fails and says that it cannot\n \t# handle this type of merge.\n \ttest_expect_code 2 git merge branch2 branch3 >output 2>&1 &&\n-\tgrep \"fatal: merge program failed\" output &&\n+\tgrep \"error: merge program failed\" output &&\n \tgrep \"Should not be doing an octopus\" output &&\n \n \t# Make sure we did not leave stray changes around when no appropriate\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460935","messageId":"20220809185429.20098-6-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 05/14] merge-index: add a new way to invoke `git-merge-one-file'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:20Z","receivedAt":"2022-08-09T19:09:42Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Since `git-merge-one-file' will be rewritten and libified, there may be\ncases where there is no executable named this way (ie. when git is\ncompiled with `SKIP_DASHED_BUILT_INS' enabled).  This adds a new way to\ninvoke this particular program even if it does not exist, by passing\n`--use=merge-one-file' to merge-index.  For now, it still forks.\n\nThe test suite and shell scripts (git-merge-octopus.sh and\ngit-merge-resolve.sh) are updated to use this new convention.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Documentation/git-merge-index.txt |  7 ++++---\n builtin/merge-index.c             | 25 ++++++++++++++++++++++---\n git-merge-octopus.sh              |  2 +-\n git-merge-resolve.sh              |  2 +-\n t/t6060-merge-index.sh            |  8 ++++----\n 5 files changed, 32 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt\nindex eea56b3154..622638a13b 100644\n--- a/Documentation/git-merge-index.txt\n+++ b/Documentation/git-merge-index.txt\n@@ -9,7 +9,7 @@ git-merge-index - Run a merge for files needing merging\n SYNOPSIS\n --------\n [verse]\n-'git merge-index' [-o] [-q] <merge-program> (-a | ( [--] <file>...) )\n+'git merge-index' [-o] [-q] (<merge-program> | --use=merge-one-file) (-a | ( [--] <file>...) )\n \n DESCRIPTION\n -----------\n@@ -44,8 +44,9 @@ code.\n Typically this is run with a script calling Git's imitation of\n the 'merge' command from the RCS package.\n \n-A sample script called 'git merge-one-file' is included in the\n-distribution.\n+A sample script called 'git merge-one-file' used to be included in the\n+distribution. This program must now be called with\n+'--use=merge-one-file'.\n \n ALERT ALERT ALERT! The Git \"merge object order\" is different from the\n RCS 'merge' program merge object order. In the above ordering, the\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 9d74b6e85c..aba3ba5694 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,4 +1,5 @@\n #include \"builtin.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n \n@@ -37,7 +38,10 @@ static int merge_one_file_spawn(struct index_state *istate,\n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tmerge_fn merge_action = merge_one_file_spawn;\n+\tstruct lock_file lock = LOCK_INIT;\n \tstruct repository *r = the_repository;\n+\tconst char *use_internal = NULL;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -45,7 +49,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tsignal(SIGCHLD, SIG_DFL);\n \n \tif (argc < 3)\n-\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n+\t\tusage(\"git merge-index [-o] [-q] (<merge-program> | --use=merge-one-file) (-a | [--] [<filename>...])\");\n \n \tif (repo_read_index(r) < 0)\n \t\tdie(\"invalid index\");\n@@ -64,6 +68,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t}\n \n \tpgm = argv[i++];\n+\tsetup_work_tree();\n+\n+\tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n+\t\tif (!strcmp(use_internal, \"merge-one-file\"))\n+\t\t\tpgm = \"git-merge-one-file\";\n+\t\telse\n+\t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n+\t}\n \n \tfor (; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n@@ -74,13 +86,20 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n \t\t\t\terr |= merge_all_index(r->index, one_shot, quiet,\n-\t\t\t\t\t\t       merge_one_file_spawn, NULL);\n+\t\t\t\t\t\t       merge_action, NULL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n \t\terr |= merge_index_path(r->index, one_shot, quiet, arg,\n-\t\t\t\t\tmerge_one_file_spawn, NULL);\n+\t\t\t\t\tmerge_action, NULL);\n+\t}\n+\n+\tif (is_lock_file_locked(&lock)) {\n+\t\tif (err)\n+\t\t\trollback_lock_file(&lock);\n+\t\telse\n+\t\t\treturn write_locked_index(r->index, &lock, COMMIT_LOCK);\n \t}\n \n \treturn err;\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\nindex 7d19d37951..2770891960 100755\n--- a/git-merge-octopus.sh\n+++ b/git-merge-octopus.sh\n@@ -100,7 +100,7 @@ do\n \tif test $? -ne 0\n \tthen\n \t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o git-merge-one-file -a ||\n+\t\tgit merge-index -o --use=merge-one-file -a ||\n \t\tOCTOPUS_FAILURE=1\n \t\tnext=$(git write-tree 2>/dev/null)\n \tfi\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\nindex 77e93121bf..e59175eb75 100755\n--- a/git-merge-resolve.sh\n+++ b/git-merge-resolve.sh\n@@ -55,7 +55,7 @@ then\n \texit 0\n else\n \techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o git-merge-one-file -a\n+\tif git merge-index -o --use=merge-one-file -a\n \tthen\n \t\texit 0\n \telse\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex bb4da4bbb2..3845a9d3cc 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -48,8 +48,8 @@ test_expect_success 'read-tree does not resolve content merge' '\n \ttest_cmp expect unmerged\n '\n \n-test_expect_success 'git merge-index git-merge-one-file resolves' '\n-\tgit merge-index git-merge-one-file -a &&\n+test_expect_success 'git merge-index --use=merge-one-file resolves' '\n+\tgit merge-index --use=merge-one-file -a &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_must_be_empty unmerged &&\n \ttest_cmp expect-merged file &&\n@@ -81,7 +81,7 @@ test_expect_success 'merge-one-file respects GIT_WORK_TREE' '\n \t export GIT_WORK_TREE &&\n \t GIT_INDEX_FILE=$PWD/merge.index &&\n \t export GIT_INDEX_FILE &&\n-\t git merge-index git-merge-one-file -a &&\n+\t git merge-index --use=merge-one-file -a &&\n \t git cat-file blob :file >work/file-index\n \t) &&\n \ttest_cmp expect-merged bare.git/work/file &&\n@@ -96,7 +96,7 @@ test_expect_success 'merge-one-file respects core.worktree' '\n \t export GIT_DIR &&\n \t git config core.worktree \"$PWD/child\" &&\n \t git read-tree -i -m base ten two &&\n-\t git merge-index git-merge-one-file -a &&\n+\t git merge-index --use=merge-one-file -a &&\n \t git cat-file blob :file >file-index\n \t) &&\n \ttest_cmp expect-merged subdir/child/file &&\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460936","messageId":"20220809185429.20098-5-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 04/14] merge-index: drop the index","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:19Z","receivedAt":"2022-08-09T19:09:44Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In an effort to reduce the usage of the global index throughout the\ncodebase, this removes references to it in `git merge-index'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-index.c | 11 ++++++-----\n 1 file changed, 6 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex f66cc515d8..9d74b6e85c 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n #include \"merge-strategies.h\"\n #include \"run-command.h\"\n@@ -38,6 +37,7 @@ static int merge_one_file_spawn(struct index_state *istate,\n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n \tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n+\tstruct repository *r = the_repository;\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -47,10 +47,11 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tif (argc < 3)\n \t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n \n-\tread_cache();\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n \n \t/* TODO: audit for interaction with sparse-index. */\n-\tensure_full_index(&the_index);\n+\tensure_full_index(r->index);\n \n \ti = 1;\n \tif (!strcmp(argv[i], \"-o\")) {\n@@ -72,13 +73,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n+\t\t\t\terr |= merge_all_index(r->index, one_shot, quiet,\n \t\t\t\t\t\t       merge_one_file_spawn, NULL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n \t\t}\n-\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n+\t\terr |= merge_index_path(r->index, one_shot, quiet, arg,\n \t\t\t\t\tmerge_one_file_spawn, NULL);\n \t}\n \n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460937","messageId":"20220809185429.20098-7-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 06/14] update-index: move add_cacheinfo() to read-cache.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:21Z","receivedAt":"2022-08-09T19:09:48Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This moves the function add_cacheinfo() that already exists in\nupdate-index.c to update-index.c, renames it add_to_index_cacheinfo(),\nand adds an `istate' parameter.  The new cache entry is returned through\na pointer passed in the parameters.  This function can return three\nvalues:\n\n - 0, when the file has been successfully added to the index;\n - ADD_TO_INDEX_CACHEINFO_INVALID_PATH, when the file does not exists;\n - ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD, when the file could not be\n   added to the index.\n\nThis will become useful in the next commit, when the three-way merge\nwill need to call this function.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/update-index.c | 25 +++++++------------------\n cache.h                |  8 ++++++++\n read-cache.c           | 35 +++++++++++++++++++++++++++++++++++\n 3 files changed, 50 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex b62249905f..2e322a58f2 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -411,27 +411,16 @@ static int process_path(const char *path, struct stat *st, int stat_errno)\n static int add_cacheinfo(unsigned int mode, const struct object_id *oid,\n \t\t\t const char *path, int stage)\n {\n-\tint len, option;\n-\tstruct cache_entry *ce;\n+\tint res;\n \n-\tif (!verify_path(path, mode))\n-\t\treturn error(\"Invalid path '%s'\", path);\n-\n-\tlen = strlen(path);\n-\tce = make_empty_cache_entry(&the_index, len);\n-\n-\toidcpy(&ce->oid, oid);\n-\tmemcpy(ce->name, path, len);\n-\tce->ce_flags = create_ce_flags(stage);\n-\tce->ce_namelen = len;\n-\tce->ce_mode = create_ce_mode(mode);\n-\tif (assume_unchanged)\n-\t\tce->ce_flags |= CE_VALID;\n-\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n-\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n-\tif (add_cache_entry(ce, option))\n+\tres = add_to_index_cacheinfo(&the_index, mode, oid, path, stage,\n+\t\t\t\t     allow_add, allow_replace, NULL);\n+\tif (res == ADD_TO_INDEX_CACHEINFO_INVALID_PATH)\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\tif (res == ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD)\n \t\treturn error(\"%s: cannot add to the index - missing --add option?\",\n \t\t\t     path);\n+\n \treport(\"add '%s'\", path);\n \treturn 0;\n }\ndiff --git a/cache.h b/cache.h\nindex 4aa1bd079d..6b5d0a2ba3 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -885,6 +885,14 @@ int remove_file_from_index(struct index_state *, const char *path);\n int add_to_index(struct index_state *, const char *path, struct stat *, int flags);\n int add_file_to_index(struct index_state *, const char *path, int flags);\n \n+#define ADD_TO_INDEX_CACHEINFO_INVALID_PATH (-1)\n+#define ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD (-2)\n+\n+int add_to_index_cacheinfo(struct index_state *, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **ce_ret);\n+\n int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);\n int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);\n void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);\ndiff --git a/read-cache.c b/read-cache.c\nindex 4de207752d..e895bf5c6a 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -1436,6 +1436,41 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti\n \treturn 0;\n }\n \n+int add_to_index_cacheinfo(struct index_state *istate, unsigned int mode,\n+\t\t\t   const struct object_id *oid, const char *path,\n+\t\t\t   int stage, int allow_add, int allow_replace,\n+\t\t\t   struct cache_entry **ce_ret)\n+{\n+\tint len, option;\n+\tstruct cache_entry *ce;\n+\n+\tif (!verify_path(path, mode))\n+\t\treturn ADD_TO_INDEX_CACHEINFO_INVALID_PATH;\n+\n+\tlen = strlen(path);\n+\tce = make_empty_cache_entry(istate, len);\n+\n+\toidcpy(&ce->oid, oid);\n+\tmemcpy(ce->name, path, len);\n+\tce->ce_flags = create_ce_flags(stage);\n+\tce->ce_namelen = len;\n+\tce->ce_mode = create_ce_mode(mode);\n+\tif (assume_unchanged)\n+\t\tce->ce_flags |= CE_VALID;\n+\toption = allow_add ? ADD_CACHE_OK_TO_ADD : 0;\n+\toption |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;\n+\n+\tif (add_index_entry(istate, ce, option)) {\n+\t\tdiscard_cache_entry(ce);\n+\t\treturn ADD_TO_INDEX_CACHEINFO_UNABLE_TO_ADD;\n+\t}\n+\n+\tif (ce_ret)\n+\t\t*ce_ret = ce;\n+\n+\treturn 0;\n+}\n+\n /*\n  * \"refresh\" does not calculate a new sha1 file or bring the\n  * cache up-to-date for mode/content changes. But what it\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460938","messageId":"20220809185429.20098-8-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 07/14] merge-one-file: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:22Z","receivedAt":"2022-08-09T19:09:50Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-one-file' from shell to C.  This port is not\ncompletely straightforward: to save precious cycles by avoiding reading\nand flushing the index repeatedly, write temporary files when an\noperation can be performed in-memory, or allow other function to use the\nrewrite without forking nor worrying about the index, the calls to\nexternal processes are replaced by calls to functions in libgit.a:\n\n - calls to `update-index --add --cacheinfo' are replaced by calls to\n   add_to_index_cacheinfo();\n\n - calls to `update-index --remove' are replaced by calls to\n   remove_file_from_index();\n\n - calls to `checkout-index -u -f' are replaced by calls to\n   checkout_entry();\n\n - calls to `unpack-file' and `merge-files' are replaced by calls to\n   read_mmblob() and xdl_merge(), respectively, to merge files\n   in-memory;\n\n - calls to `checkout-index -f --stage=2' are removed, as this is needed\n   to have the correct permission bits on the merged file from the\n   script, but not in the C version;\n\n - calls to `update-index' are replaced by calls to add_file_to_index().\n\nThe bulk of the rewrite is done in a new file in libgit.a,\nmerge-strategies.c.  This will enable the resolve and octopus strategies\nto directly call it instead of forking.\n\nThis also fixes a bug present in the original script: instead of\nchecking if a _regular_ file exists when a file exists in the branch to\nmerge, but not in our branch, the rewritten version checks if a file of\nany kind (ie. a directory, ...) exists.  This fixes the tests t6035.14,\nwhere the branch to merge had a new file, `a/b', but our branch had a\ndirectory there; it should have failed because a directory exists, but\nit did not because there was no regular file called `a/b'.  This test is\nnow marked as successful.\n\nThis also teaches `merge-index' to call merge_three_way() (when invoked\nwith `--use=merge-one-file') without forking using a new callback,\nmerge_one_file_func().\n\nTo avoid any issue with a shrinking index because of the merge function\nused (directly in the process or by forking), as described earlier, the\niterator of the loop of merge_all_index() is increased by the number of\nentries with the same name, minus the difference between the number of\nentries in the index before and after the merge.\n\nThis should handle a shrinking index correctly, but could lead to issues\nwith a growing index.  However, this case is not treated, as there is no\ncallback that can produce such a case.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                        |   2 +-\n builtin.h                       |   1 +\n builtin/merge-index.c           |   4 +-\n builtin/merge-one-file.c        |  92 ++++++++++++++\n git-merge-one-file.sh           | 167 -------------------------\n git.c                           |   1 +\n merge-strategies.c              | 208 +++++++++++++++++++++++++++++++-\n merge-strategies.h              |  13 ++\n t/t6060-merge-index.sh          |   2 +-\n t/t6415-merge-dir-to-symlink.sh |   2 +-\n 10 files changed, 317 insertions(+), 175 deletions(-)\n create mode 100644 builtin/merge-one-file.c\n delete mode 100755 git-merge-one-file.sh\n\ndiff --git a/Makefile b/Makefile\nindex 40d1be4e5e..e2e6cbbb41 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -631,7 +631,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-one-file.sh\n SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n@@ -1186,6 +1185,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n BUILTIN_OBJS += builtin/merge-tree.o\ndiff --git a/builtin.h b/builtin.h\nindex 40e9ecc848..cdbe91bbe8 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -182,6 +182,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex aba3ba5694..a242b357f8 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -72,9 +72,11 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \n \tif (skip_prefix(pgm, \"--use=\", &use_internal)) {\n \t\tif (!strcmp(use_internal, \"merge-one-file\"))\n-\t\t\tpgm = \"git-merge-one-file\";\n+\t\t\tmerge_action = merge_one_file_func;\n \t\telse\n \t\t\tdie(_(\"git merge-index: unknown internal program %s\"), use_internal);\n+\n+\t\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n \t}\n \n \tfor (; i < argc; i++) {\ndiff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\nnew file mode 100644\nindex 0000000000..ec718cc1c9\n--- /dev/null\n+++ b/builtin/merge-one-file.c\n@@ -0,0 +1,92 @@\n+/*\n+ * Builtin \"git merge-one-file\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-one-file.sh, written by Linus Torvalds.\n+ *\n+ * This is the git per-file merge utility, called with\n+ *\n+ *   argv[1] - original file object name (or empty)\n+ *   argv[2] - file in branch1 object name (or empty)\n+ *   argv[3] - file in branch2 object name (or empty)\n+ *   argv[4] - pathname in repository\n+ *   argv[5] - original file mode (or empty)\n+ *   argv[6] - file in branch1 mode (or empty)\n+ *   argv[7] - file in branch2 mode (or empty)\n+ *\n+ * Handle some trivial cases. The _really_ trivial cases have been\n+ * handled already by git read-tree, but that one doesn't do any merges\n+ * that might change the tree layout.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"lockfile.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_one_file_usage[] =\n+\t\"git merge-one-file <orig blob> <our blob> <their blob> <path> \"\n+\t\"<orig mode> <our mode> <their mode>\\n\\n\"\n+\t\"Blob ids and modes should be empty for missing files.\";\n+\n+static int read_param(const char *name, const char *arg_blob, const char *arg_mode,\n+\t\t      struct object_id *blob, struct object_id **p_blob, unsigned int *mode)\n+{\n+\tif (*arg_blob && !get_oid_hex(arg_blob, blob)) {\n+\t\tchar *last;\n+\n+\t\t*p_blob = blob;\n+\t\t*mode = strtol(arg_mode, &last, 8);\n+\n+\t\tif (*last)\n+\t\t\treturn error(_(\"invalid '%s' mode: expected nothing, got '%c'\"), name, *last);\n+\t\telse if (!(S_ISREG(*mode) || S_ISDIR(*mode) || S_ISLNK(*mode)))\n+\t\t\treturn error(_(\"invalid '%s' mode: %o\"), name, *mode);\n+\t} else if (!*arg_blob && *arg_mode)\n+\t\treturn error(_(\"no '%s' object id given, but a mode was still given.\"), name);\n+\n+\treturn 0;\n+}\n+\n+int cmd_merge_one_file(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct object_id orig_blob, our_blob, their_blob,\n+\t\t*p_orig_blob = NULL, *p_our_blob = NULL, *p_their_blob = NULL;\n+\tunsigned int orig_mode = 0, our_mode = 0, their_mode = 0, ret = 0;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tstruct repository *r = the_repository;\n+\n+\tif (argc != 8)\n+\t\tusage(builtin_merge_one_file_usage);\n+\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (read_param(\"orig\", argv[1], argv[5], &orig_blob,\n+\t\t       &p_orig_blob, &orig_mode))\n+\t\tret = -1;\n+\n+\tif (read_param(\"our\", argv[2], argv[6], &our_blob,\n+\t\t       &p_our_blob, &our_mode))\n+\t\tret = -1;\n+\n+\tif (read_param(\"their\", argv[3], argv[7], &their_blob,\n+\t\t       &p_their_blob, &their_mode))\n+\t\tret = -1;\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = merge_three_way(r->index, p_orig_blob, p_our_blob, p_their_blob,\n+\t\t\t      argv[4], orig_mode, our_mode, their_mode);\n+\n+\tif (ret) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn !!ret;\n+\t}\n+\n+\treturn write_locked_index(r->index, &lock, COMMIT_LOCK);\n+}\ndiff --git a/git-merge-one-file.sh b/git-merge-one-file.sh\ndeleted file mode 100755\nindex f6d9852d2f..0000000000\n--- a/git-merge-one-file.sh\n+++ /dev/null\n@@ -1,167 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) Linus Torvalds, 2005\n-#\n-# This is the git per-file merge script, called with\n-#\n-#   $1 - original file SHA1 (or empty)\n-#   $2 - file in branch1 SHA1 (or empty)\n-#   $3 - file in branch2 SHA1 (or empty)\n-#   $4 - pathname in repository\n-#   $5 - original file mode (or empty)\n-#   $6 - file in branch1 mode (or empty)\n-#   $7 - file in branch2 mode (or empty)\n-#\n-# Handle some trivial cases.. The _really_ trivial cases have\n-# been handled already by git read-tree, but that one doesn't\n-# do any merges that might change the tree layout.\n-\n-USAGE='<orig blob> <our blob> <their blob> <path>'\n-USAGE=\"$USAGE <orig mode> <our mode> <their mode>\"\n-LONG_USAGE=\"usage: git merge-one-file $USAGE\n-\n-Blob ids and modes should be empty for missing files.\"\n-\n-SUBDIRECTORY_OK=Yes\n-. git-sh-setup\n-cd_to_toplevel\n-require_work_tree\n-\n-if test $# != 7\n-then\n-\techo \"$LONG_USAGE\"\n-\texit 1\n-fi\n-\n-case \"${1:-.}${2:-.}${3:-.}\" in\n-#\n-# Deleted in both or deleted in one and unchanged in the other\n-#\n-\"$1..\" | \"$1.$1\" | \"$1$1.\")\n-\tif { test -z \"$6\" && test \"$5\" != \"$7\"; } ||\n-\t   { test -z \"$7\" && test \"$5\" != \"$6\"; }\n-\tthen\n-\t\techo \"ERROR: File $4 deleted on one branch but had its\" >&2\n-\t\techo \"ERROR: permissions changed on the other.\" >&2\n-\t\texit 1\n-\tfi\n-\n-\tif test -n \"$2\"\n-\tthen\n-\t\techo \"Removing $4\"\n-\telse\n-\t\t# read-tree checked that index matches HEAD already,\n-\t\t# so we know we do not have this path tracked.\n-\t\t# there may be an unrelated working tree file here,\n-\t\t# which we should just leave unmolested.  Make sure\n-\t\t# we do not have it in the index, though.\n-\t\texec git update-index --remove -- \"$4\"\n-\tfi\n-\tif test -f \"$4\"\n-\tthen\n-\t\trm -f -- \"$4\" &&\n-\t\trmdir -p \"$(expr \"z$4\" : 'z\\(.*\\)/')\" 2>/dev/null || :\n-\tfi &&\n-\t\texec git update-index --remove -- \"$4\"\n-\t;;\n-\n-#\n-# Added in one.\n-#\n-\".$2.\")\n-\t# the other side did not add and we added so there is nothing\n-\t# to be done, except making the path merged.\n-\texec git update-index --add --cacheinfo \"$6\" \"$2\" \"$4\"\n-\t;;\n-\"..$3\")\n-\techo \"Adding $4\"\n-\tif test -f \"$4\"\n-\tthen\n-\t\techo \"ERROR: untracked $4 is overwritten by the merge.\" >&2\n-\t\texit 1\n-\tfi\n-\tgit update-index --add --cacheinfo \"$7\" \"$3\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Added in both, identically (check for same permissions).\n-#\n-\".$3$2\")\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\techo \"ERROR: File $4 added identically in both branches,\" >&2\n-\t\techo \"ERROR: but permissions conflict $6->$7.\" >&2\n-\t\texit 1\n-\tfi\n-\techo \"Adding $4\"\n-\tgit update-index --add --cacheinfo \"$6\" \"$2\" \"$4\" &&\n-\t\texec git checkout-index -u -f -- \"$4\"\n-\t;;\n-\n-#\n-# Modified in both, but differently.\n-#\n-\"$1$2$3\" | \".$2$3\")\n-\n-\tcase \",$6,$7,\" in\n-\t*,120000,*)\n-\t\techo \"ERROR: $4: Not merging symbolic link changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\t*,160000,*)\n-\t\techo \"ERROR: $4: Not merging conflicting submodule changes.\" >&2\n-\t\texit 1\n-\t\t;;\n-\tesac\n-\n-\tsrc1=$(git unpack-file $2)\n-\tsrc2=$(git unpack-file $3)\n-\tcase \"$1\" in\n-\t'')\n-\t\techo \"Added $4 in both, but differently.\"\n-\t\torig=$(git unpack-file $(git hash-object /dev/null))\n-\t\t;;\n-\t*)\n-\t\techo \"Auto-merging $4\"\n-\t\torig=$(git unpack-file $1)\n-\t\t;;\n-\tesac\n-\n-\tgit merge-file \"$src1\" \"$orig\" \"$src2\"\n-\tret=$?\n-\tmsg=\n-\tif test $ret != 0 || test -z \"$1\"\n-\tthen\n-\t\tmsg='content conflict'\n-\t\tret=1\n-\tfi\n-\n-\t# Create the working tree file, using \"our tree\" version from the\n-\t# index, and then store the result of the merge.\n-\tgit checkout-index -f --stage=2 -- \"$4\" && cat \"$src1\" >\"$4\" || exit 1\n-\trm -f -- \"$orig\" \"$src1\" \"$src2\"\n-\n-\tif test \"$6\" != \"$7\"\n-\tthen\n-\t\tif test -n \"$msg\"\n-\t\tthen\n-\t\t\tmsg=\"$msg, \"\n-\t\tfi\n-\t\tmsg=\"${msg}permissions conflict: $5->$6,$7\"\n-\t\tret=1\n-\tfi\n-\n-\tif test $ret != 0\n-\tthen\n-\t\techo \"ERROR: $msg in $4\" >&2\n-\t\texit 1\n-\tfi\n-\texec git update-index -- \"$4\"\n-\t;;\n-\n-*)\n-\techo \"ERROR: $4: Not handling case $1 -> $2 -> $3\" >&2\n-\t;;\n-esac\n-exit 1\ndiff --git a/git.c b/git.c\nindex e5d62fa5a9..f5d3c6cb39 100644\n--- a/git.c\n+++ b/git.c\n@@ -561,6 +561,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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 },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 418c9dd710..373b69c10b 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,198 @@\n #include \"cache.h\"\n+#include \"dir.h\"\n+#include \"entry.h\"\n #include \"merge-strategies.h\"\n+#include \"xdiff-interface.h\"\n+\n+static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n+\t\t\t\t     const struct object_id *oid, const char *path,\n+\t\t\t\t     int checkout)\n+{\n+\tstruct cache_entry *ce;\n+\tint res;\n+\n+\tres = add_to_index_cacheinfo(istate, mode, oid, path, 0, 1, 1, &ce);\n+\tif (res == -1)\n+\t\treturn error(_(\"Invalid path '%s'\"), path);\n+\telse if (res == -2)\n+\t\treturn -1;\n+\n+\tif (checkout) {\n+\t\tstruct checkout state = CHECKOUT_INIT;\n+\n+\t\tstate.istate = istate;\n+\t\tstate.force = 1;\n+\t\tstate.base_dir = \"\";\n+\t\tstate.base_dir_len = 0;\n+\n+\t\tif (checkout_entry(ce, &state, NULL, NULL) < 0)\n+\t\t\treturn error(_(\"%s: cannot checkout file\"), path);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int merge_one_file_deleted(struct index_state *istate,\n+\t\t\t\t  const struct object_id *our_blob,\n+\t\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t\t  unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif ((!our_blob && orig_mode != their_mode) ||\n+\t    (!their_blob && orig_mode != our_mode))\n+\t\treturn error(_(\"File %s deleted on one branch but had its \"\n+\t\t\t       \"permissions changed on the other.\"), path);\n+\n+\tif (our_blob) {\n+\t\tprintf(_(\"Removing %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\tremove_path(path);\n+\t}\n+\n+\tif (remove_file_from_index(istate, path))\n+\t\treturn error(\"%s: cannot remove from the index\", path);\n+\treturn 0;\n+}\n+\n+static int do_merge_one_file(struct index_state *istate,\n+\t\t\t     const struct object_id *orig_blob,\n+\t\t\t     const struct object_id *our_blob,\n+\t\t\t     const struct object_id *their_blob, const char *path,\n+\t\t\t     unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tint ret, i, dest;\n+\tssize_t written;\n+\tmmbuffer_t result = {NULL, 0};\n+\tmmfile_t mmfs[3];\n+\txmparam_t xmp = {{0}};\n+\n+\tif (our_mode == S_IFLNK || their_mode == S_IFLNK)\n+\t\treturn error(_(\"%s: Not merging symbolic link changes.\"), path);\n+\telse if (our_mode == S_IFGITLINK || their_mode == S_IFGITLINK)\n+\t\treturn error(_(\"%s: Not merging conflicting submodule changes.\"), path);\n+\n+\tif (orig_blob) {\n+\t\tprintf(_(\"Auto-merging %s\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, orig_blob);\n+\t} else {\n+\t\tprintf(_(\"Added %s in both, but differently.\\n\"), path);\n+\t\tread_mmblob(mmfs + 0, null_oid());\n+\t}\n+\n+\tread_mmblob(mmfs + 1, our_blob);\n+\tread_mmblob(mmfs + 2, their_blob);\n+\n+\txmp.level = XDL_MERGE_ZEALOUS_ALNUM;\n+\txmp.style = 0;\n+\txmp.favor = 0;\n+\n+\tret = xdl_merge(mmfs + 0, mmfs + 1, mmfs + 2, &xmp, &result);\n+\n+\tfor (i = 0; i < 3; i++)\n+\t\tfree(mmfs[i].ptr);\n+\n+\tif (ret < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error(_(\"Failed to execute internal merge\"));\n+\t}\n+\n+\tif (ret > 0 || !orig_blob)\n+\t\tret = error(_(\"content conflict in %s\"), path);\n+\tif (our_mode != their_mode)\n+\t\tret = error(_(\"permission conflict: %o->%o,%o in %s\"),\n+\t\t\t    orig_mode, our_mode, their_mode, path);\n+\n+\tunlink(path);\n+\tif ((dest = open(path, O_WRONLY | O_CREAT, our_mode)) < 0) {\n+\t\tfree(result.ptr);\n+\t\treturn error_errno(_(\"failed to open file '%s'\"), path);\n+\t}\n+\n+\twritten = write_in_full(dest, result.ptr, result.size);\n+\tclose(dest);\n+\n+\tfree(result.ptr);\n+\n+\tif (written < 0)\n+\t\treturn error_errno(_(\"failed to write to '%s'\"), path);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn add_file_to_index(istate, path, 0);\n+}\n+\n+int merge_three_way(struct index_state *istate,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n+{\n+\tif (orig_blob &&\n+\t    ((!our_blob && !their_blob) ||\n+\t     (!their_blob && our_blob && oideq(orig_blob, our_blob)) ||\n+\t     (!our_blob && their_blob && oideq(orig_blob, their_blob)))) {\n+\t\t/* Deleted in both or deleted in one and unchanged in the other. */\n+\t\treturn merge_one_file_deleted(istate, our_blob, their_blob, path,\n+\t\t\t\t\t      orig_mode, our_mode, their_mode);\n+\t} else if (!orig_blob && our_blob && !their_blob) {\n+\t\t/*\n+\t\t * Added in ours.  The other side did not add and we\n+\t\t * added so there is nothing to be done, except making\n+\t\t * the path merged.\n+\t\t */\n+\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 0);\n+\t} else if (!orig_blob && !our_blob && their_blob) {\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\tif (file_exists(path))\n+\t\t\treturn error(_(\"untracked %s is overwritten by the merge.\"), path);\n+\n+\t\treturn add_merge_result_to_index(istate, their_mode, their_blob, path, 1);\n+\t} else if (!orig_blob && our_blob && their_blob &&\n+\t\t   oideq(our_blob, their_blob)) {\n+\t\t/* Added in both, identically (check for same permissions). */\n+\t\tif (our_mode != their_mode)\n+\t\t\treturn error(_(\"File %s added identically in both branches, \"\n+\t\t\t\t       \"but permissions conflict %o->%o.\"),\n+\t\t\t\t     path, our_mode, their_mode);\n+\n+\t\tprintf(_(\"Adding %s\\n\"), path);\n+\n+\t\treturn add_merge_result_to_index(istate, our_mode, our_blob, path, 1);\n+\t} else if (our_blob && their_blob) {\n+\t\t/* Modified in both, but differently. */\n+\t\treturn do_merge_one_file(istate,\n+\t\t\t\t\t orig_blob, our_blob, their_blob, path,\n+\t\t\t\t\t orig_mode, our_mode, their_mode);\n+\t} else {\n+\t\tchar orig_hex[GIT_MAX_HEXSZ] = {0}, our_hex[GIT_MAX_HEXSZ] = {0},\n+\t\t\ttheir_hex[GIT_MAX_HEXSZ] = {0};\n+\n+\t\tif (orig_blob)\n+\t\t\toid_to_hex_r(orig_hex, orig_blob);\n+\t\tif (our_blob)\n+\t\t\toid_to_hex_r(our_hex, our_blob);\n+\t\tif (their_blob)\n+\t\t\toid_to_hex_r(their_hex, their_blob);\n+\n+\t\treturn error(_(\"%s: Not handling case %s -> %s -> %s\"),\n+\t\t\t     path, orig_hex, our_hex, their_hex);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_one_file_func(struct index_state *istate,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data)\n+{\n+\treturn merge_three_way(istate,\n+\t\t\t       orig_blob, our_blob, their_blob, path,\n+\t\t\t       orig_mode, our_mode, their_mode);\n+}\n \n static int merge_entry(struct index_state *istate, unsigned int pos,\n \t\t       const char *path, int *err, merge_fn fn, void *data)\n@@ -54,7 +247,7 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data)\n {\n \tint err = 0, ret;\n-\tunsigned int i;\n+\tunsigned int i, prev_nr;\n \n \t/* TODO: audit for interaction with sparse-index. */\n \tensure_full_index(istate);\n@@ -63,10 +256,17 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\tif (!ce_stage(ce))\n \t\t\tcontinue;\n \n+\t\tprev_nr = istate->cache_nr;\n \t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n-\t\tif (ret > 0)\n-\t\t\ti += ret - 1;\n-\t\telse if (ret == -1)\n+\t\tif (ret > 0) {\n+\t\t\t/*\n+\t\t\t * Don't bother handling an index that has\n+\t\t\t * grown, since merge_one_file_func() can't grow\n+\t\t\t * it, and merge_one_file_spawn() can't change\n+\t\t\t * it.\n+\t\t\t */\n+\t\t\ti += ret - (prev_nr - istate->cache_nr) - 1;\n+\t\t} else if (ret == -1)\n \t\t\treturn -1;\n \n \t\tif (err && !oneshot) {\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 88f476f170..8705a550ca 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -3,6 +3,12 @@\n \n #include \"object.h\"\n \n+int merge_three_way(struct index_state *istate,\n+\t\t    const struct object_id *orig_blob,\n+\t\t    const struct object_id *our_blob,\n+\t\t    const struct object_id *their_blob, const char *path,\n+\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode);\n+\n typedef int (*merge_fn)(struct index_state *istate,\n \t\t\tconst struct object_id *orig_blob,\n \t\t\tconst struct object_id *our_blob,\n@@ -10,6 +16,13 @@ typedef int (*merge_fn)(struct index_state *istate,\n \t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n \t\t\tvoid *data);\n \n+int merge_one_file_func(struct index_state *istate,\n+\t\t\tconst struct object_id *orig_blob,\n+\t\t\tconst struct object_id *our_blob,\n+\t\t\tconst struct object_id *their_blob, const char *path,\n+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n+\t\t\tvoid *data);\n+\n int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n \t\t     const char *path, merge_fn fn, void *data);\n int merge_all_index(struct index_state *istate, int oneshot, int quiet,\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 3845a9d3cc..9976996c80 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -70,7 +70,7 @@ test_expect_success 'merge-one-file fails without a work tree' '\n \t(cd bare.git &&\n \t GIT_INDEX_FILE=$PWD/merge.index &&\n \t export GIT_INDEX_FILE &&\n-\t test_must_fail git merge-index git-merge-one-file -a\n+\t test_must_fail git merge-index --use=merge-one-file -a\n \t)\n '\n \ndiff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\nindex 2655e295f5..10bc5eb8c4 100755\n--- a/t/t6415-merge-dir-to-symlink.sh\n+++ b/t/t6415-merge-dir-to-symlink.sh\n@@ -99,7 +99,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n \ttest -h a/b\n '\n \n-test_expect_failure 'do not lose untracked in merge (resolve)' '\n+test_expect_success 'do not lose untracked in merge (resolve)' '\n \tgit reset --hard &&\n \tgit checkout baseline^0 &&\n \t>a/b/c/e &&\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460939","messageId":"20220809185429.20098-9-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:23Z","receivedAt":"2022-08-09T19:09:51Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-resolve' from shell to C.  As for `git\nmerge-one-file', this port is not completely straightforward and removes\ncalls to external processes to avoid reading and writing the index over\nand over again.\n\n - The call to `update-index -q --refresh' is replaced by a call to\n   refresh_index().\n\n - The call to `read-tree' is replaced by a call to unpack_trees() (and\n   all the setup needed).\n\n - The call to `write-tree' is replaced by a call to\n   cache_tree_update().  This call is wrapped in a new function,\n   write_tree().  It is made to mimick write_index_as_tree() with\n   WRITE_TREE_SILENT flag, but without locking the index; this is taken\n   care directly in merge_strategies_resolve().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to the new merge_all_index() function.\n\nThe index is read in cmd_merge_resolve(), and is wrote back by\nmerge_strategies_resolve().  This is to accomodate future applications:\nin `git-merge', the index has already been read when the merge strategy\nis called, so it would be redundant to read it again when the builtin\nwill be able to use merge_strategies_resolve() directly.\n\nThe parameters of merge_strategies_resolve() will be surprising at first\nglance: why using a commit list for `bases' and `remote', where we could\nuse an oid array, and a pointer to an oid?  Because, in a later commit,\ntry_merge_strategy() will be able to call merge_strategies_resolve()\ndirectly, and it already uses a commit list for `bases' (`common') and\n`remote' (`remoteheads'), and a string for `head_arg'.  To reduce\nfrictions later, merge_strategies_resolve() takes the same types of\nparameters.\n\nmerge_strategies_resolve() locks the index only once, at the beginning\nof the merge, and releases it when the merge has been completed.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-resolve.c |  63 ++++++++++++++++++\n git-merge-resolve.sh    |  64 -------------------\n git.c                   |   1 +\n merge-strategies.c      | 137 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   5 ++\n 7 files changed, 208 insertions(+), 65 deletions(-)\n create mode 100644 builtin/merge-resolve.c\n delete mode 100755 git-merge-resolve.sh\n\ndiff --git a/Makefile b/Makefile\nindex e2e6cbbb41..0c18acb979 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -631,7 +631,6 @@ SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-merge-octopus.sh\n-SCRIPT_SH += git-merge-resolve.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1188,6 +1187,7 @@ BUILTIN_OBJS += builtin/merge-index.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\n+BUILTIN_OBJS += builtin/merge-resolve.o\n BUILTIN_OBJS += builtin/merge-tree.o\n BUILTIN_OBJS += builtin/merge.o\n BUILTIN_OBJS += builtin/mktag.o\ndiff --git a/builtin.h b/builtin.h\nindex cdbe91bbe8..4627229944 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -184,6 +184,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix);\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix);\n int cmd_merge_tree(int argc, const char **argv, const char *prefix);\n int cmd_mktag(int argc, const char **argv, const char *prefix);\n int cmd_mktree(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\nnew file mode 100644\nindex 0000000000..a51158ebf8\n--- /dev/null\n+++ b/builtin/merge-resolve.c\n@@ -0,0 +1,63 @@\n+/*\n+ * Builtin \"git merge-resolve\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n+ * Hamano.\n+ *\n+ * Resolve two trees, using enhanced multi-base read-tree.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_resolve_usage[] =\n+\t\"git merge-resolve <bases>... -- <head> <remote>\";\n+\n+int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tconst char *head = NULL;\n+\tstruct commit_list *bases = NULL, *remote = NULL;\n+\tstruct commit_list **next_base = &bases;\n+\tstruct repository *r = the_repository;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_resolve_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (!strcmp(argv[i], \"--\"))\n+\t\t\tsep_seen = 1;\n+\t\telse if (!strcmp(argv[i], \"-h\"))\n+\t\t\tusage(builtin_merge_resolve_usage);\n+\t\telse if (sep_seen && !head)\n+\t\t\thead = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n+\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tcommit_list_insert(commit, &remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\treturn merge_strategies_resolve(r, bases, head, remote);\n+}\ndiff --git a/git-merge-resolve.sh b/git-merge-resolve.sh\ndeleted file mode 100755\nindex e59175eb75..0000000000\n--- a/git-merge-resolve.sh\n+++ /dev/null\n@@ -1,64 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Linus Torvalds\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two trees, using enhanced multi-base read-tree.\n-\n-. git-sh-setup\n-\n-# Abort if index does not match HEAD\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Give up if we are given two or more remotes -- not handling octopus.\n-case \"$remotes\" in\n-?*' '?*)\n-\texit 2 ;;\n-esac\n-\n-# Give up if this is a baseless merge.\n-if test '' = \"$bases\"\n-then\n-\texit 2\n-fi\n-\n-git update-index -q --refresh\n-git read-tree -u -m --aggressive $bases $head $remotes || exit 2\n-echo \"Trying simple merge.\"\n-if result_tree=$(git write-tree 2>/dev/null)\n-then\n-\texit 0\n-else\n-\techo \"Simple merge failed, trying Automatic merge.\"\n-\tif git merge-index -o --use=merge-one-file -a\n-\tthen\n-\t\texit 0\n-\telse\n-\t\texit 1\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex f5d3c6cb39..09d222da88 100644\n--- a/git.c\n+++ b/git.c\n@@ -565,6 +565,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\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-resolve\", cmd_merge_resolve, 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 },\n \t{ \"mktag\", cmd_mktag, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 373b69c10b..30f225ae5f 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,9 +1,34 @@\n #include \"cache.h\"\n+#include \"cache-tree.h\"\n #include \"dir.h\"\n #include \"entry.h\"\n+#include \"lockfile.h\"\n #include \"merge-strategies.h\"\n+#include \"unpack-trees.h\"\n #include \"xdiff-interface.h\"\n \n+static int check_index_is_head(struct repository *r, const char *head_arg)\n+{\n+\tstruct commit *head_commit;\n+\tstruct tree *head_tree;\n+\tstruct object_id head;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tget_oid(head_arg, &head);\n+\thead_commit = lookup_commit_reference(r, &head);\n+\thead_tree = repo_get_commit_tree(r, head_commit);\n+\n+\tif (repo_index_has_changes(r, head_tree, &sb)) {\n+\t\terror(_(\"Your local changes to the following files \"\n+\t\t\t\"would be overwritten by merge:\\n  %s\"),\n+\t\t      sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int add_merge_result_to_index(struct index_state *istate, unsigned int mode,\n \t\t\t\t     const struct object_id *oid, const char *path,\n \t\t\t\t     int checkout)\n@@ -280,3 +305,115 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\terror(_(\"merge program failed\"));\n \treturn err;\n }\n+\n+static int merge_trees(struct repository *r, struct tree_desc *t,\n+\t\t       int nr, int aggressive)\n+{\n+\tstruct unpack_trees_options opts;\n+\n+\trefresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);\n+\n+\tmemset(&opts, 0, sizeof(opts));\n+\topts.head_idx = 1;\n+\topts.src_index = r->index;\n+\topts.dst_index = r->index;\n+\topts.merge = 1;\n+\topts.update = 1;\n+\topts.aggressive = aggressive;\n+\n+\tif (nr == 1)\n+\t\topts.fn = oneway_merge;\n+\telse if (nr == 2) {\n+\t\topts.fn = twoway_merge;\n+\t\topts.initial_checkout = is_index_unborn(r->index);\n+\t} else if (nr >= 3) {\n+\t\topts.fn = threeway_merge;\n+\t\topts.head_idx = nr - 1;\n+\t}\n+\n+\tif (unpack_trees(nr, t, &opts))\n+\t\treturn -1;\n+\treturn 0;\n+}\n+\n+static int add_tree(struct tree *tree, struct tree_desc *t)\n+{\n+\tif (parse_tree(tree))\n+\t\treturn -1;\n+\n+\tinit_tree_desc(t, tree->buffer, tree->size);\n+\treturn 0;\n+}\n+\n+static int write_tree(struct repository *r)\n+{\n+\tint was_valid;\n+\twas_valid = r->index->cache_tree &&\n+\t\tcache_tree_fully_valid(r->index->cache_tree);\n+\n+\tif (!was_valid && cache_tree_update(r->index, WRITE_TREE_SILENT) < 0)\n+\t\treturn WRITE_TREE_UNMERGED_INDEX;\n+\treturn 0;\n+}\n+\n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct commit_list *i;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint nr = 0, ret = 0;\n+\n+\t/* Abort if index does not match head */\n+\tif (check_index_is_head(r, head_arg))\n+\t\treturn 2;\n+\n+\t/*\n+\t * Give up if we are given two or more remotes.  Not handling\n+\t * octopus.\n+\t */\n+\tif (remote && remote->next)\n+\t\treturn 2;\n+\n+\t/* Give up if this is a baseless merge. */\n+\tif (!bases)\n+\t\treturn 2;\n+\n+\tputs(_(\"Trying simple merge.\"));\n+\n+\tfor (i = bases; i && i->item; i = i->next) {\n+\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (head_arg) {\n+\t\tstruct object_id head;\n+\t\tstruct tree *tree;\n+\n+\t\tget_oid(head_arg, &head);\n+\t\ttree = parse_tree_indirect(&head);\n+\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn 2;\n+\t}\n+\n+\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n+\t\treturn 2;\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tif (merge_trees(r, t, nr, 1)) {\n+\t\trollback_lock_file(&lock);\n+\t\treturn 2;\n+\t}\n+\n+\tif (write_tree(r)) {\n+\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n+\t\tret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n+\t}\n+\n+\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n+\t\treturn !!error(_(\"unable to write new index file\"));\n+\treturn !!ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex 8705a550ca..bba4bf999c 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -1,6 +1,7 @@\n #ifndef MERGE_STRATEGIES_H\n #define MERGE_STRATEGIES_H\n \n+#include \"commit.h\"\n #include \"object.h\"\n \n int merge_three_way(struct index_state *istate,\n@@ -28,4 +29,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n \t\t    merge_fn fn, void *data);\n \n+int merge_strategies_resolve(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n+\n #endif /* MERGE_STRATEGIES_H */\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460940","messageId":"20220809185429.20098-10-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 09/14] merge-recursive: move better_branch_name() to merge.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:24Z","receivedAt":"2022-08-09T19:09:53Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"better_branch_name() will be used by merge-octopus once it is rewritten\nin C, so instead of duplicating it, this moves this function\npreventively inside an appropriate file in libgit.a.  This function is\nalso renamed to reflect its usage by merge strategies.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge-recursive.c | 16 ++--------------\n cache.h                   |  2 +-\n merge.c                   | 12 ++++++++++++\n 3 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex b9acbf5d34..ae429c8514 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -8,18 +8,6 @@\n static const char builtin_merge_recursive_usage[] =\n \t\"git %s <base>... -- <head> <remote> ...\";\n \n-static char *better_branch_name(const char *branch)\n-{\n-\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n-\tchar *name;\n-\n-\tif (strlen(branch) != the_hash_algo->hexsz)\n-\t\treturn xstrdup(branch);\n-\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n-\tname = getenv(githead_env);\n-\treturn xstrdup(name ? name : branch);\n-}\n-\n int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n {\n \tconst struct object_id *bases[21];\n@@ -75,8 +63,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)\n \tif (get_oid(o.branch2, &h2))\n \t\tdie(_(\"could not resolve ref '%s'\"), o.branch2);\n \n-\to.branch1 = better1 = better_branch_name(o.branch1);\n-\to.branch2 = better2 = better_branch_name(o.branch2);\n+\to.branch1 = better1 = merge_get_better_branch_name(o.branch1);\n+\to.branch2 = better2 = merge_get_better_branch_name(o.branch2);\n \n \tif (o.verbosity >= 3)\n \t\tprintf(_(\"Merging %s with %s\\n\"), o.branch1, o.branch2);\ndiff --git a/cache.h b/cache.h\nindex 6b5d0a2ba3..61ac42fa43 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1916,7 +1916,7 @@ int checkout_fast_forward(struct repository *r,\n \t\t\t  const struct object_id *from,\n \t\t\t  const struct object_id *to,\n \t\t\t  int overwrite_ignore);\n-\n+char *merge_get_better_branch_name(const char *branch);\n \n int sane_execvp(const char *file, char *const argv[]);\n \ndiff --git a/merge.c b/merge.c\nindex 2382ff66d3..d87bfd4824 100644\n--- a/merge.c\n+++ b/merge.c\n@@ -102,3 +102,15 @@ int checkout_fast_forward(struct repository *r,\n \t\treturn error(_(\"unable to write new index file\"));\n \treturn 0;\n }\n+\n+char *merge_get_better_branch_name(const char *branch)\n+{\n+\tstatic char githead_env[8 + GIT_MAX_HEXSZ + 1];\n+\tchar *name;\n+\n+\tif (strlen(branch) != the_hash_algo->hexsz)\n+\t\treturn xstrdup(branch);\n+\txsnprintf(githead_env, sizeof(githead_env), \"GITHEAD_%s\", branch);\n+\tname = getenv(githead_env);\n+\treturn xstrdup(name ? name : branch);\n+}\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460941","messageId":"20220809185429.20098-12-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 11/14] merge: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:26Z","receivedAt":"2022-08-09T19:09:56Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex f7c92c0e64..0ab2993ab2 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -44,6 +44,7 @@\n #include \"commit-reach.h\"\n #include \"wt-status.h\"\n #include \"commit-graph.h\"\n+#include \"merge-strategies.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -774,6 +775,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n \t\treturn clean ? 0 : 1;\n+\t} else if (!strcmp(strategy, \"resolve\")) {\n+\t\treturn merge_strategies_resolve(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460943","messageId":"20220809185429.20098-11-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 10/14] merge-octopus: rewrite in C","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:25Z","receivedAt":"2022-08-09T19:09:57Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This rewrites `git merge-octopus' from shell to C.  As for the two last\nconversions, this port removes calls to external processes to avoid\nreading and writing the index over and over again.\n\n - Calls to `read-tree -u -m (--aggressive)?' are replaced by calls to\n   unpack_trees().\n\n - The call to `write-tree' is replaced by a call to write_tree().\n\n - The call to `diff-index ...' is replaced by a call to\n   repo_index_has_changes().\n\n - The call to `merge-index', needed to invoke `git merge-one-file', is\n   replaced by a call to merge_all_index().\n\nThe index is read in cmd_merge_octopus(), and is written back by\nmerge_strategies_octopus(), for the same reason as merge-resolve.\n\nHere too, merge_strategies_octopus() takes two commit lists and a string\nto reduce friction when try_merge_strategies() will be modified to call\nit directly.  It also locks the index at the start of the merge, and\nreleases it at the end.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n Makefile                |   2 +-\n builtin.h               |   1 +\n builtin/merge-octopus.c |  63 +++++++++++++++\n git-merge-octopus.sh    | 112 --------------------------\n git.c                   |   1 +\n merge-strategies.c      | 171 ++++++++++++++++++++++++++++++++++++++++\n merge-strategies.h      |   3 +\n 7 files changed, 240 insertions(+), 113 deletions(-)\n create mode 100644 builtin/merge-octopus.c\n delete mode 100755 git-merge-octopus.sh\n\ndiff --git a/Makefile b/Makefile\nindex 0c18acb979..9fe1e72f6e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -630,7 +630,6 @@ unexport CDPATH\n SCRIPT_SH += git-bisect.sh\n SCRIPT_SH += git-difftool--helper.sh\n SCRIPT_SH += git-filter-branch.sh\n-SCRIPT_SH += git-merge-octopus.sh\n SCRIPT_SH += git-mergetool.sh\n SCRIPT_SH += git-quiltimport.sh\n SCRIPT_SH += git-request-pull.sh\n@@ -1184,6 +1183,7 @@ BUILTIN_OBJS += builtin/mailsplit.o\n BUILTIN_OBJS += builtin/merge-base.o\n BUILTIN_OBJS += builtin/merge-file.o\n BUILTIN_OBJS += builtin/merge-index.o\n+BUILTIN_OBJS += builtin/merge-octopus.o\n BUILTIN_OBJS += builtin/merge-one-file.o\n BUILTIN_OBJS += builtin/merge-ours.o\n BUILTIN_OBJS += builtin/merge-recursive.o\ndiff --git a/builtin.h b/builtin.h\nindex 4627229944..9305dda166 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -180,6 +180,7 @@ int cmd_maintenance(int argc, const char **argv, const char *prefix);\n int cmd_merge(int argc, const char **argv, const char *prefix);\n int cmd_merge_base(int argc, const char **argv, const char *prefix);\n int cmd_merge_index(int argc, const char **argv, const char *prefix);\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix);\n int cmd_merge_ours(int argc, const char **argv, const char *prefix);\n int cmd_merge_file(int argc, const char **argv, const char *prefix);\n int cmd_merge_one_file(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge-octopus.c b/builtin/merge-octopus.c\nnew file mode 100644\nindex 0000000000..ff3089bfca\n--- /dev/null\n+++ b/builtin/merge-octopus.c\n@@ -0,0 +1,63 @@\n+/*\n+ * Builtin \"git merge-octopus\"\n+ *\n+ * Copyright (c) 2020 Alban Gruin\n+ *\n+ * Based on git-merge-octopus.sh, written by Junio C Hamano.\n+ *\n+ * Resolve two or more trees.\n+ */\n+\n+#include \"cache.h\"\n+#include \"builtin.h\"\n+#include \"commit.h\"\n+#include \"merge-strategies.h\"\n+\n+static const char builtin_merge_octopus_usage[] =\n+\t\"git merge-octopus [<bases>...] -- <head> <remote1> <remote2> [<remotes>...]\";\n+\n+int cmd_merge_octopus(int argc, const char **argv, const char *prefix)\n+{\n+\tint i, sep_seen = 0;\n+\tstruct commit_list *bases = NULL, *remotes = NULL;\n+\tstruct commit_list **next_base = &bases, **next_remote = &remotes;\n+\tconst char *head_arg = NULL;\n+\tstruct repository *r = the_repository;\n+\n+\tif (argc < 5)\n+\t\tusage(builtin_merge_octopus_usage);\n+\n+\tsetup_work_tree();\n+\tif (repo_read_index(r) < 0)\n+\t\tdie(\"invalid index\");\n+\n+\t/*\n+\t * The first parameters up to -- are merge bases; the rest are\n+\t * heads.\n+\t */\n+\tfor (i = 1; i < argc; i++) {\n+\t\tif (strcmp(argv[i], \"--\") == 0)\n+\t\t\tsep_seen = 1;\n+\t\telse if (strcmp(argv[i], \"-h\") == 0)\n+\t\t\tusage(builtin_merge_octopus_usage);\n+\t\telse if (sep_seen && !head_arg)\n+\t\t\thead_arg = argv[i];\n+\t\telse {\n+\t\t\tstruct object_id oid;\n+\t\t\tstruct commit *commit;\n+\n+\t\t\tif (get_oid(argv[i], &oid))\n+\t\t\t\tdie(\"object %s not found.\", argv[i]);\n+\n+\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n+\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n+\n+\t\t\tif (sep_seen)\n+\t\t\t\tnext_remote = commit_list_append(commit, next_remote);\n+\t\t\telse\n+\t\t\t\tnext_base = commit_list_append(commit, next_base);\n+\t\t}\n+\t}\n+\n+\treturn merge_strategies_octopus(r, bases, head_arg, remotes);\n+}\ndiff --git a/git-merge-octopus.sh b/git-merge-octopus.sh\ndeleted file mode 100755\nindex 2770891960..0000000000\n--- a/git-merge-octopus.sh\n+++ /dev/null\n@@ -1,112 +0,0 @@\n-#!/bin/sh\n-#\n-# Copyright (c) 2005 Junio C Hamano\n-#\n-# Resolve two or more trees.\n-#\n-\n-. git-sh-setup\n-\n-LF='\n-'\n-\n-# The first parameters up to -- are merge bases; the rest are heads.\n-bases= head= remotes= sep_seen=\n-for arg\n-do\n-\tcase \",$sep_seen,$head,$arg,\" in\n-\t*,--,)\n-\t\tsep_seen=yes\n-\t\t;;\n-\t,yes,,*)\n-\t\thead=$arg\n-\t\t;;\n-\t,yes,*)\n-\t\tremotes=\"$remotes$arg \"\n-\t\t;;\n-\t*)\n-\t\tbases=\"$bases$arg \"\n-\t\t;;\n-\tesac\n-done\n-\n-# Reject if this is not an octopus -- resolve should be used instead.\n-case \"$remotes\" in\n-?*' '?*)\n-\t;;\n-*)\n-\texit 2 ;;\n-esac\n-\n-# MRC is the current \"merge reference commit\"\n-# MRT is the current \"merge result tree\"\n-\n-if ! git diff-index --quiet --cached HEAD --\n-then\n-    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n-    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'\n-    exit 2\n-fi\n-MRC=$(git rev-parse --verify -q $head)\n-MRT=$(git write-tree)\n-NON_FF_MERGE=0\n-OCTOPUS_FAILURE=0\n-for SHA1 in $remotes\n-do\n-\tcase \"$OCTOPUS_FAILURE\" in\n-\t1)\n-\t\t# We allow only last one to have a hand-resolvable\n-\t\t# conflicts.  Last round failed and we still had\n-\t\t# a head to merge.\n-\t\tgettextln \"Automated merge did not work.\"\n-\t\tgettextln \"Should not be doing an octopus.\"\n-\t\texit 2\n-\tesac\n-\n-\teval pretty_name=\\${GITHEAD_$SHA1:-$SHA1}\n-\tif test \"$SHA1\" = \"$pretty_name\"\n-\tthen\n-\t\tSHA1_UP=\"$(echo \"$SHA1\" | tr a-z A-Z)\"\n-\t\teval pretty_name=\\${GITHEAD_$SHA1_UP:-$pretty_name}\n-\tfi\n-\tcommon=$(git merge-base --all $SHA1 $MRC) ||\n-\t\tdie \"$(eval_gettext \"Unable to find common commit with \\$pretty_name\")\"\n-\n-\tcase \"$LF$common$LF\" in\n-\t*\"$LF$SHA1$LF\"*)\n-\t\teval_gettextln \"Already up to date with \\$pretty_name\"\n-\t\tcontinue\n-\t\t;;\n-\tesac\n-\n-\tif test \"$common,$NON_FF_MERGE\" = \"$MRC,0\"\n-\tthen\n-\t\t# The first head being merged was a fast-forward.\n-\t\t# Advance MRC to the head being merged, and use that\n-\t\t# tree as the intermediate result of the merge.\n-\t\t# We still need to count this as part of the parent set.\n-\n-\t\teval_gettextln \"Fast-forwarding to: \\$pretty_name\"\n-\t\tgit read-tree -u -m $head $SHA1 || exit\n-\t\tMRC=$SHA1 MRT=$(git write-tree)\n-\t\tcontinue\n-\tfi\n-\n-\tNON_FF_MERGE=1\n-\n-\teval_gettextln \"Trying simple merge with \\$pretty_name\"\n-\tgit read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2\n-\tnext=$(git write-tree 2>/dev/null)\n-\tif test $? -ne 0\n-\tthen\n-\t\tgettextln \"Simple merge did not work, trying automatic merge.\"\n-\t\tgit merge-index -o --use=merge-one-file -a ||\n-\t\tOCTOPUS_FAILURE=1\n-\t\tnext=$(git write-tree 2>/dev/null)\n-\tfi\n-\n-\tMRC=\"$MRC $SHA1\"\n-\tMRT=$next\n-done\n-\n-exit \"$OCTOPUS_FAILURE\"\ndiff --git a/git.c b/git.c\nindex 09d222da88..7a5e506c64 100644\n--- a/git.c\n+++ b/git.c\n@@ -560,6 +560,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n \t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-octopus\", cmd_merge_octopus, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-one-file\", cmd_merge_one_file, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/merge-strategies.c b/merge-strategies.c\nindex 30f225ae5f..3e8255614b 100644\n--- a/merge-strategies.c\n+++ b/merge-strategies.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"cache-tree.h\"\n+#include \"commit-reach.h\"\n #include \"dir.h\"\n #include \"entry.h\"\n #include \"lockfile.h\"\n@@ -417,3 +418,173 @@ int merge_strategies_resolve(struct repository *r,\n \t\treturn !!error(_(\"unable to write new index file\"));\n \treturn !!ret;\n }\n+\n+static int octopus_fast_forward(struct repository *r, const char *branch_name,\n+\t\t\t\tstruct tree *tree_head, struct tree *current_tree)\n+{\n+\t/*\n+\t * The first head being merged was a fast-forward.  Advance the\n+\t * reference commit to the head being merged, and use that tree\n+\t * as the intermediate result of the merge.  We still need to\n+\t * count this as part of the parent set.\n+\t */\n+\tstruct tree_desc t[2];\n+\n+\tprintf(_(\"Fast-forwarding to: %s\\n\"), branch_name);\n+\n+\tinit_tree_desc(t, tree_head->buffer, tree_head->size);\n+\tif (add_tree(current_tree, t + 1))\n+\t\treturn -1;\n+\tif (merge_trees(r, t, 2, 0))\n+\t\treturn -1;\n+\tif (write_tree(r))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int octopus_do_merge(struct repository *r, const char *branch_name,\n+\t\t\t    struct commit_list *common, struct tree *current_tree,\n+\t\t\t    struct tree *reference_tree)\n+{\n+\tstruct tree_desc t[MAX_UNPACK_TREES];\n+\tstruct commit_list *i;\n+\tint nr = 0, ret = 0;\n+\n+\tprintf(_(\"Trying simple merge with %s\\n\"), branch_name);\n+\n+\tfor (i = common; i; i = i->next) {\n+\t\tstruct tree *tree = repo_get_commit_tree(r, i->item);\n+\t\tif (add_tree(tree, t + (nr++)))\n+\t\t\treturn -1;\n+\t}\n+\n+\tif (add_tree(reference_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (add_tree(current_tree, t + (nr++)))\n+\t\treturn -1;\n+\tif (merge_trees(r, t, nr, 1))\n+\t\treturn 2;\n+\n+\tif (write_tree(r)) {\n+\t\tputs(_(\"Simple merge did not work, trying automatic merge.\"));\n+\t\tret = !!merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n+\t\twrite_tree(r);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remotes)\n+{\n+\tint ff_merge = 1, ret = 0, nr_references = 1;\n+\tstruct commit **reference_commits, *head_commit;\n+\tstruct tree *reference_tree, *head_tree;\n+\tstruct commit_list *i;\n+\tstruct object_id head;\n+\tstruct lock_file lock = LOCK_INIT;\n+\n+\t/*\n+\t * Reject if this is not an octopus -- resolve should be used\n+\t * instead.\n+\t */\n+\tif (commit_list_count(remotes) < 2)\n+\t\treturn 2;\n+\n+\t/* Abort if index does not match head */\n+\tif (check_index_is_head(r, head_arg))\n+\t\treturn 2;\n+\n+\tget_oid(head_arg, &head);\n+\thead_commit = lookup_commit_reference(r, &head);\n+\thead_tree = repo_get_commit_tree(r, head_commit);\n+\n+\tCALLOC_ARRAY(reference_commits, commit_list_count(remotes) + 1);\n+\treference_commits[0] = head_commit;\n+\treference_tree = head_tree;\n+\n+\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n+\n+\tfor (i = remotes; i && i->item; i = i->next) {\n+\t\tstruct commit *c = i->item;\n+\t\tstruct object_id *oid = &c->object.oid;\n+\t\tstruct tree *current_tree = repo_get_commit_tree(r, c);\n+\t\tstruct commit_list *common, *j;\n+\t\tchar *branch_name = merge_get_better_branch_name(oid_to_hex(oid));\n+\t\tint up_to_date = 0;\n+\n+\t\tcommon = repo_get_merge_bases_many(r, c, nr_references, reference_commits);\n+\t\tif (!common) {\n+\t\t\terror(_(\"Unable to find common commit with %s\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\n+\t\t\tret = 2;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If `oid' is reachable from `HEAD', we're already up\n+\t\t * to date.\n+\t\t */\n+\t\tfor (j = common; j; j = j->next) {\n+\t\t\tif (oideq(&j->item->object.oid, oid)) {\n+\t\t\t\tup_to_date = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (up_to_date) {\n+\t\t\tprintf(_(\"Already up to date with %s\\n\"), branch_name);\n+\n+\t\t\tfree(branch_name);\n+\t\t\tfree_commit_list(common);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/*\n+\t\t * If we could fast-forward so far and `HEAD' is the\n+\t\t * single merge base with the current `remote' revision,\n+\t\t * keep fast-forwarding.\n+\t\t */\n+\t\tif (ff_merge && common && !common->next && nr_references == 1 &&\n+\t\t    oideq(&common->item->object.oid,\n+\t\t\t  &reference_commits[0]->object.oid)) {\n+\t\t\tret = octopus_fast_forward(r, branch_name, head_tree, current_tree);\n+\t\t\tnr_references = 0;\n+\t\t} else {\n+\t\t\tret = octopus_do_merge(r, branch_name, common,\n+\t\t\t\t\t       current_tree, reference_tree);\n+\t\t\tff_merge = 0;\n+\t\t}\n+\n+\t\tfree(branch_name);\n+\t\tfree_commit_list(common);\n+\n+\t\tif (ret == -1 || ret == 2)\n+\t\t\tbreak;\n+\t\telse if (ret && i->next) {\n+\t\t\t/*\n+\t\t\t * We allow only last one to have a\n+\t\t\t * hand-resolvable conflicts.  Last round failed\n+\t\t\t * and we still had a head to merge.\n+\t\t\t */\n+\t\t\tputs(_(\"Automated merge did not work.\"));\n+\t\t\tputs(_(\"Should not be doing an octopus.\"));\n+\n+\t\t\tret = 2;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\treference_commits[nr_references++] = c;\n+\t\treference_tree = lookup_tree(r, &r->index->cache_tree->oid);\n+\t}\n+\n+\tfree(reference_commits);\n+\twrite_locked_index(r->index, &lock, COMMIT_LOCK);\n+\n+\treturn ret;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nindex bba4bf999c..8de2249ee6 100644\n--- a/merge-strategies.h\n+++ b/merge-strategies.h\n@@ -32,5 +32,8 @@ int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n int merge_strategies_resolve(struct repository *r,\n \t\t\t     struct commit_list *bases, const char *head_arg,\n \t\t\t     struct commit_list *remote);\n+int merge_strategies_octopus(struct repository *r,\n+\t\t\t     struct commit_list *bases, const char *head_arg,\n+\t\t\t     struct commit_list *remote);\n \n #endif /* MERGE_STRATEGIES_H */\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460942","messageId":"20220809185429.20098-14-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 13/14] sequencer: use the \"resolve\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:28Z","receivedAt":"2022-08-09T19:09:58Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"resolve\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 14 +++++++++++---\n 1 file changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5f22b7cd37..0e5e6cbb24 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -37,6 +37,7 @@\n #include \"reset.h\"\n #include \"branch.h\"\n #include \"log-tree.h\"\n+#include \"merge-strategies.h\"\n \n #define GIT_REFLOG_ACTION \"GIT_REFLOG_ACTION\"\n \n@@ -2314,9 +2315,16 @@ static int do_pick_commit(struct repository *r,\n \n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n-\t\tres |= try_merge_command(r, opts->strategy,\n-\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-\t\t\t\t\tcommon, oid_to_hex(&head), remotes);\n+\n+\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else {\n+\t\t\tres |= try_merge_command(r, opts->strategy,\n+\t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n+\t\t\t\t\t\t common, oid_to_hex(&head), remotes);\n+\t\t}\n+\n \t\tfree_commit_list(common);\n \t\tfree_commit_list(remotes);\n \t}\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460944","messageId":"20220809185429.20098-13-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 12/14] merge: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:27Z","receivedAt":"2022-08-09T19:10:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches `git merge' to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/merge.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 0ab2993ab2..a44a6b810b 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -778,6 +778,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t} else if (!strcmp(strategy, \"resolve\")) {\n \t\treturn merge_strategies_resolve(the_repository, common,\n \t\t\t\t\t\thead_arg, remoteheads);\n+\t} else if (!strcmp(strategy, \"octopus\")) {\n+\t\treturn merge_strategies_octopus(the_repository, common,\n+\t\t\t\t\t\thead_arg, remoteheads);\n \t} else {\n \t\treturn try_merge_command(the_repository,\n \t\t\t\t\t strategy, xopts_nr, xopts,\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460945","messageId":"20220809185429.20098-15-alban.gruin@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v8 14/14] sequencer: use the \"octopus\" strategy without forking","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2022-08-09T18:54:29Z","receivedAt":"2022-08-09T19:10:10Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This teaches the sequencer to invoke the \"octopus\" strategy with a\nfunction call instead of forking.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 0e5e6cbb24..00a3620584 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2319,6 +2319,9 @@ static int do_pick_commit(struct repository *r,\n \t\tif (!strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n+\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t\trepo_read_index(r);\n+\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else {\n \t\t\tres |= try_merge_command(r, opts->strategy,\n \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n-- \n2.37.1.412.gcfdce49ffd\n\n"},{"id":"460949","messageId":"o759r3qn-nqn9-oq22-p90o-2nrn24085n80@tzk.qr","threadId":"53755","inReplyTo":"20220809185429.20098-6-alban.gruin@gmail.com","subject":"Re: [PATCH v8 05/14] merge-index: add a new way to invoke `git-merge-one-file'","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-08-09T21:36:32Z","receivedAt":"2022-08-09T21:36:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Tue, 9 Aug 2022, Alban Gruin wrote:\n\n> Since `git-merge-one-file' will be rewritten and libified, there may be\n> cases where there is no executable named this way (ie. when git is\n> compiled with `SKIP_DASHED_BUILT_INS' enabled).  This adds a new way to\n> invoke this particular program even if it does not exist, by passing\n> `--use=merge-one-file' to merge-index.  For now, it still forks.\n\nI read up about Stolee's and Phillip's suggestion, and about Junio chiming\nin, but I have to point out that all the objections against special-casing\n`!strcmp(pgm, \"git-merge-one-file`\") share one fundamental flaw: they fail\nto acknowledge that we will _have_ to special-case this value once we turn\n`merge-one-file` into a built-in.\n\nAnd the reason is: there might be scripts out there that expect `git\nmerge-index git-merge-one-file [...]` to continue to work even when\nbuilding Git with `SKIP_DASHED_BUILT_INS`.\n\nIn light of that, I would like to point out that we really _must_ revert\nto `if (!strcmp(pgm, \"git-merge-one-file\"))`, ie. to what v6 did (see\nhttps://lore.kernel.org/git/20201124115315.13311-7-alban.gruin@gmail.com/).\n\nAnd since we must do that anyway, I do not see any need for the `--use`\noption at all, it just complicates the usage and does not really provide\nany benefit that I can see.\n\nOn the upside: skipping the `--use` option will dramatically simplify this\npatch.\n\nSorry for not catching this earlier.\n\n> diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt\n> [...]\n> @@ -44,8 +44,9 @@ code.\n>  Typically this is run with a script calling Git's imitation of\n>  the 'merge' command from the RCS package.\n>\n> -A sample script called 'git merge-one-file' is included in the\n> -distribution.\n> +A sample script called 'git merge-one-file' used to be included in the\n> +distribution. This program must now be called with\n> +'--use=merge-one-file'.\n\nIt probably makes more sense to just drop this paragraph because we will\nno longer provide that sample script.\n\nThanks,\nDscho\n"},{"id":"460950","messageId":"2r992r19-or36-733r-1139-4575n9o6o23s@tzk.qr","threadId":"53755","inReplyTo":"20220809185429.20098-8-alban.gruin@gmail.com","subject":"Re: [PATCH v8 07/14] merge-one-file: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-08-09T22:01:30Z","receivedAt":"2022-08-09T22:02:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nwhat an incredible amount of careful work. Thank you for doing this.\n\nA few minor comments:\n\nOn Tue, 9 Aug 2022, Alban Gruin wrote:\n\n> diff --git a/builtin/merge-one-file.c b/builtin/merge-one-file.c\n> new file mode 100644\n> index 0000000000..ec718cc1c9\n> --- /dev/null\n> +++ b/builtin/merge-one-file.c\n> @@ -0,0 +1,92 @@\n> +/*\n> + * Builtin \"git merge-one-file\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n\nThere have been claims that it is still March 2020 (see e.g.\nhttps://ismarchoveryet.com/), but I believe that those are jokes and that\nwe're really in the year 2022 now. It should be safe to adjust the text\naccordingly.\n\n:-)\n\n> [...]\n> +int merge_three_way(struct index_state *istate,\n> +\t\t    const struct object_id *orig_blob,\n> +\t\t    const struct object_id *our_blob,\n> +\t\t    const struct object_id *their_blob, const char *path,\n> +\t\t    unsigned int orig_mode, unsigned int our_mode, unsigned int their_mode)\n> +{\n> [...]\n> +}\n> +\n> +int merge_one_file_func(struct index_state *istate,\n> +\t\t\tconst struct object_id *orig_blob,\n> +\t\t\tconst struct object_id *our_blob,\n> +\t\t\tconst struct object_id *their_blob, const char *path,\n> +\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n> +\t\t\tvoid *data)\n> +{\n> +\treturn merge_three_way(istate,\n> +\t\t\t       orig_blob, our_blob, their_blob, path,\n> +\t\t\t       orig_mode, our_mode, their_mode);\n> +}\n\nI have only read the patch series until this point (and plan on continuing\nwith the remaining patches tomorrow), so I might be wrong, but... is there\nany other user of `merge_three_way()` left? If not (and I suspect this is\nthe case), then the `merge_three_way()` code could be moved into\n`merge_one_file_func()`.\n\n> [...]\n> diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\n> index 3845a9d3cc..9976996c80 100755\n> --- a/t/t6060-merge-index.sh\n> +++ b/t/t6060-merge-index.sh\n> @@ -70,7 +70,7 @@ test_expect_success 'merge-one-file fails without a work tree' '\n>  \t(cd bare.git &&\n>  \t GIT_INDEX_FILE=$PWD/merge.index &&\n>  \t export GIT_INDEX_FILE &&\n> -\t test_must_fail git merge-index git-merge-one-file -a\n> +\t test_must_fail git merge-index --use=merge-one-file -a\n\nThis hunk probably wanted to live in [PATCH v8 05/14] merge-index: add a\nnew way to invoke `git-merge-one-file', but as I pointed out in my reply\nto that patch: I do not think that we have to introduce that `--use=<...>`\noption at all.\n\n>  \t)\n>  '\n>\n> diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh\n> index 2655e295f5..10bc5eb8c4 100755\n> --- a/t/t6415-merge-dir-to-symlink.sh\n> +++ b/t/t6415-merge-dir-to-symlink.sh\n> @@ -99,7 +99,7 @@ test_expect_success SYMLINKS 'a/b was resolved as symlink' '\n>  \ttest -h a/b\n>  '\n>\n> -test_expect_failure 'do not lose untracked in merge (resolve)' '\n> +test_expect_success 'do not lose untracked in merge (resolve)' '\n\nVery, very nice.\n\nThank you!\nDscho\n\n>  \tgit reset --hard &&\n>  \tgit checkout baseline^0 &&\n>  \t>a/b/c/e &&\n> --\n> 2.37.1.412.gcfdce49ffd\n>\n>\n"},{"id":"460982","messageId":"0e1be1b9-0a69-0531-8b2c-0f0f618925b7@gmail.com","threadId":"53755","inReplyTo":"o759r3qn-nqn9-oq22-p90o-2nrn24085n80@tzk.qr","subject":"Re: [PATCH v8 05/14] merge-index: add a new way to invoke `git-merge-one-file'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-08-10T13:14:03Z","receivedAt":"2022-08-10T13:14:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 09/08/2022 22:36, Johannes Schindelin wrote:\n> Hi Alban,\n> \n> On Tue, 9 Aug 2022, Alban Gruin wrote:\n> \n>> Since `git-merge-one-file' will be rewritten and libified, there may be\n>> cases where there is no executable named this way (ie. when git is\n>> compiled with `SKIP_DASHED_BUILT_INS' enabled).  This adds a new way to\n>> invoke this particular program even if it does not exist, by passing\n>> `--use=merge-one-file' to merge-index.  For now, it still forks.\n> \n> I read up about Stolee's and Phillip's suggestion,\n\nI thought I was in favor of special casing git-merge-one-file. Stolee \nseems to have been worried about someone passing \"git merge-one-file\" \nbut I think we only accept a program name and not a program plus \narguments so that shouldn't be a problem.\n\n> and about Junio chiming\n> in, but I have to point out that all the objections against special-casing\n> `!strcmp(pgm, \"git-merge-one-file`\") share one fundamental flaw: they fail\n> to acknowledge that we will _have_ to special-case this value once we turn\n> `merge-one-file` into a built-in.\n> \n> And the reason is: there might be scripts out there that expect `git\n> merge-index git-merge-one-file [...]` to continue to work even when\n> building Git with `SKIP_DASHED_BUILT_INS`.\n> \n> In light of that, I would like to point out that we really _must_ revert\n> to `if (!strcmp(pgm, \"git-merge-one-file\"))`, ie. to what v6 did (see\n> https://lore.kernel.org/git/20201124115315.13311-7-alban.gruin@gmail.com/).\n> \n> And since we must do that anyway, I do not see any need for the `--use`\n> option at all, it just complicates the usage and does not really provide\n> any benefit that I can see.\n> \n> On the upside: skipping the `--use` option will dramatically simplify this\n> patch.\n\nI'd be happy to see the '--use' option dropped as well.\n\nThanks for continuing to work on this Alban, I'm sorry I've not had time \nto look at it properly since v1, I'll try and take a good look at this \nversion.\n\nBest Wishes\n\nPhillip\n\n> Sorry for not catching this earlier.\n> \n>> diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt\n>> [...]\n>> @@ -44,8 +44,9 @@ code.\n>>   Typically this is run with a script calling Git's imitation of\n>>   the 'merge' command from the RCS package.\n>>\n>> -A sample script called 'git merge-one-file' is included in the\n>> -distribution.\n>> +A sample script called 'git merge-one-file' used to be included in the\n>> +distribution. This program must now be called with\n>> +'--use=merge-one-file'.\n> \n> It probably makes more sense to just drop this paragraph because we will\n> no longer provide that sample script.\n> \n> Thanks,\n> Dscho\n\n"},{"id":"460992","messageId":"08ea1eec-58fb-cbfa-d405-0d4159c99515@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-9-alban.gruin@gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-08-10T15:03:47Z","receivedAt":"2022-08-10T15:03:58Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 09/08/2022 19:54, Alban Gruin wrote:\n> This rewrites `git merge-resolve' from shell to C.  As for `git\n> merge-one-file', this port is not completely straightforward and removes\n> calls to external processes to avoid reading and writing the index over\n> and over again.\n> \n>   - The call to `update-index -q --refresh' is replaced by a call to\n>     refresh_index().\n> \n>   - The call to `read-tree' is replaced by a call to unpack_trees() (and\n>     all the setup needed).\n> \n>   - The call to `write-tree' is replaced by a call to\n>     cache_tree_update().  This call is wrapped in a new function,\n>     write_tree().  It is made to mimick write_index_as_tree() with\n>     WRITE_TREE_SILENT flag, but without locking the index; this is taken\n>     care directly in merge_strategies_resolve().\n> \n>   - The call to `diff-index ...' is replaced by a call to\n>     repo_index_has_changes().\n> \n>   - The call to `merge-index', needed to invoke `git merge-one-file', is\n>     replaced by a call to the new merge_all_index() function.\n> \n> The index is read in cmd_merge_resolve(), and is wrote back by\n> merge_strategies_resolve().  This is to accomodate future applications:\n> in `git-merge', the index has already been read when the merge strategy\n> is called, so it would be redundant to read it again when the builtin\n> will be able to use merge_strategies_resolve() directly.\n> \n> The parameters of merge_strategies_resolve() will be surprising at first\n> glance: why using a commit list for `bases' and `remote', where we could\n> use an oid array, and a pointer to an oid?  Because, in a later commit,\n> try_merge_strategy() will be able to call merge_strategies_resolve()\n> directly, and it already uses a commit list for `bases' (`common') and\n> `remote' (`remoteheads'), and a string for `head_arg'.  To reduce\n> frictions later, merge_strategies_resolve() takes the same types of\n> parameters.\n\ngit-merge-resolve will happily merge three trees, unfortunately using \nlists of commits will break that.\n\n> merge_strategies_resolve() locks the index only once, at the beginning\n> of the merge, and releases it when the merge has been completed.\n> \n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n> diff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\n> new file mode 100644\n> index 0000000000..a51158ebf8\n> --- /dev/null\n> +++ b/builtin/merge-resolve.c\n> @@ -0,0 +1,63 @@\n> +/*\n> + * Builtin \"git merge-resolve\"\n> + *\n> + * Copyright (c) 2020 Alban Gruin\n> + *\n> + * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n> + * Hamano.\n> + *\n> + * Resolve two trees, using enhanced multi-base read-tree.\n> + */\n> +\n> +#include \"cache.h\"\n> +#include \"builtin.h\"\n> +#include \"merge-strategies.h\"\n> +\n> +static const char builtin_merge_resolve_usage[] =\n> +\t\"git merge-resolve <bases>... -- <head> <remote>\";\n> +\n> +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n> +{\n> +\tint i, sep_seen = 0;\n> +\tconst char *head = NULL;\n> +\tstruct commit_list *bases = NULL, *remote = NULL;\n> +\tstruct commit_list **next_base = &bases;\n> +\tstruct repository *r = the_repository;\n> +\n> +\tif (argc < 5)\n> +\t\tusage(builtin_merge_resolve_usage);\n\nI think it would be better to call parse_options() and then check argc. \nThat would give better error messages for unknown options and supports \n'-h' for free.\n\nI think we also need to call git_config(). I see that read-tree respects \nsubmodule.recurse so I think we need the same here. I suspect we should \nalso be reading the merge config to respect merge.conflictStyle.\n\n> +\tsetup_work_tree();\n> +\tif (repo_read_index(r) < 0)\n> +\t\tdie(\"invalid index\");\n\nThis should probably be marked for translation.\n\n> +\n> +\t/*\n> +\t * The first parameters up to -- are merge bases; the rest are\n> +\t * heads.\n> +\t */\n> +\tfor (i = 1; i < argc; i++) {\n> +\t\tif (!strcmp(argv[i], \"--\"))\n> +\t\t\tsep_seen = 1;\n> +\t\telse if (!strcmp(argv[i], \"-h\"))\n> +\t\t\tusage(builtin_merge_resolve_usage);\n> +\t\telse if (sep_seen && !head)\n> +\t\t\thead = argv[i];\n> +\t\telse {\n> +\t\t\tstruct object_id oid;\n> +\t\t\tstruct commit *commit;\n> +\n> +\t\t\tif (get_oid(argv[i], &oid))\n> +\t\t\t\tdie(\"object %s not found.\", argv[i]);\n\ntranslation here as well.\n\n> +\t\t\tcommit = oideq(&oid, r->hash_algo->empty_tree) ?\n> +\t\t\t\tNULL : lookup_commit_or_die(&oid, argv[i]);\n\nAs I said above, git-merge-resolve should be callable with trees I think.\n\n> +\n> +\t\t\tif (sep_seen)\n> +\t\t\t\tcommit_list_insert(commit, &remote);\n> +\t\t\telse\n> +\t\t\t\tnext_base = commit_list_append(commit, next_base);\n> +\t\t}\n> +\t}\n> +\n> +\treturn merge_strategies_resolve(r, bases, head, remote);\n> +}\n> diff --git a/merge-strategies.c b/merge-strategies.c\n> index 373b69c10b..30f225ae5f 100644\n> --- a/merge-strategies.c\n> +++ b/merge-strategies.c\n> @@ -1,9 +1,34 @@\n>   #include \"cache.h\"\n> +#include \"cache-tree.h\"\n>   #include \"dir.h\"\n>   #include \"entry.h\"\n> +#include \"lockfile.h\"\n>   #include \"merge-strategies.h\"\n> +#include \"unpack-trees.h\"\n>   #include \"xdiff-interface.h\"\n>   \n> +static int check_index_is_head(struct repository *r, const char *head_arg)\n> +{\n> +\tstruct commit *head_commit;\n> +\tstruct tree *head_tree;\n> +\tstruct object_id head;\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\n> +\tget_oid(head_arg, &head);\n> +\thead_commit = lookup_commit_reference(r, &head);\n> +\thead_tree = repo_get_commit_tree(r, head_commit);\n\nCan this all be replaced by a call to parse_tree_indirect(), we should \nalso handle an invalid HEAD.\n\n> +\n> +\tif (repo_index_has_changes(r, head_tree, &sb)) {\n> +\t\terror(_(\"Your local changes to the following files \"\n> +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n> +\t\t      sb.buf);\n\nThis matches the script but I wonder why that did not check for unstaged \nchanges.\n\n\n> +int merge_strategies_resolve(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n\nAs well as the commit vs tree comments above I think that we should be \ngetting the callers to parse head rather than passing a string. Both \nbuiltin/merge.c and sequencer.c have a struct commit we can use.\n\n> +\t\t\t     struct commit_list *remote)\n> +{\n> +\tstruct tree_desc t[MAX_UNPACK_TREES];\n> +\tstruct commit_list *i;\n> +\tstruct lock_file lock = LOCK_INIT;\n> +\tint nr = 0, ret = 0;\n> +\n> +\t/* Abort if index does not match head */\n> +\tif (check_index_is_head(r, head_arg))\n> +\t\treturn 2;\n> +\n> +\t/*\n> +\t * Give up if we are given two or more remotes.  Not handling\n> +\t * octopus.\n> +\t */\n> +\tif (remote && remote->next)\n> +\t\treturn 2;\n> +\n> +\t/* Give up if this is a baseless merge. */\n> +\tif (!bases)\n> +\t\treturn 2;\n> +\n> +\tputs(_(\"Trying simple merge.\"));\n> +\n> +\tfor (i = bases; i && i->item; i = i->next) {\n> +\t\tif (add_tree(repo_get_commit_tree(r, i->item), t + (nr++)))\n\nThis needs to check that we're not overrunning the end of t as \nbuiltin/read-tree.c:list_trees() does.\n\n\nExcept for the tree issue the conversion of the script looks correct to \nme and you have been careful to preserve the exit values.\n\nBest Wishes\n\nPhillip\n\n> +\t\t\treturn 2;\n> +\t}\n> +\n> +\tif (head_arg) {\n> +\t\tstruct object_id head;\n> +\t\tstruct tree *tree;\n> +\n> +\t\tget_oid(head_arg, &head);\n> +\t\ttree = parse_tree_indirect(&head);\n> +\n> +\t\tif (add_tree(tree, t + (nr++)))\n> +\t\t\treturn 2;\n> +\t}\n> +\n> +\tif (remote && add_tree(repo_get_commit_tree(r, remote->item), t + (nr++)))\n> +\t\treturn 2;\n> +\n> +\trepo_hold_locked_index(r, &lock, LOCK_DIE_ON_ERROR);\n> +\n> +\tif (merge_trees(r, t, nr, 1)) {\n> +\t\trollback_lock_file(&lock);\n> +\t\treturn 2;\n> +\t}\n> +\n> +\tif (write_tree(r)) {\n> +\t\tputs(_(\"Simple merge failed, trying Automatic merge.\"));\n> +\t\tret = merge_all_index(r->index, 1, 0, merge_one_file_func, NULL);\n> +\t}\n> +\n> +\tif (write_locked_index(r->index, &lock, COMMIT_LOCK))\n> +\t\treturn !!error(_(\"unable to write new index file\"));\n> +\treturn !!ret;\n> +}\n> diff --git a/merge-strategies.h b/merge-strategies.h\n> index 8705a550ca..bba4bf999c 100644\n> --- a/merge-strategies.h\n> +++ b/merge-strategies.h\n> @@ -1,6 +1,7 @@\n>   #ifndef MERGE_STRATEGIES_H\n>   #define MERGE_STRATEGIES_H\n>   \n> +#include \"commit.h\"\n>   #include \"object.h\"\n>   \n>   int merge_three_way(struct index_state *istate,\n> @@ -28,4 +29,8 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n>   int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n>   \t\t    merge_fn fn, void *data);\n>   \n> +int merge_strategies_resolve(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remote);\n> +\n>   #endif /* MERGE_STRATEGIES_H */\n"},{"id":"461032","messageId":"xmqqilmzkd7p.fsf@gitster.g","threadId":"53755","inReplyTo":"08ea1eec-58fb-cbfa-d405-0d4159c99515@gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-10T21:20:26Z","receivedAt":"2022-08-10T21:20:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> git-merge-resolve will happily merge three trees, unfortunately using\n> lists of commits will break that.\n\nTrue.\n\nWhile I agree that it would make sense to rewrite some strategies in\nC, I do not quite see the point of redoing this particular one.  Its\nsimplicity is one of the only few remaining shining points in the\n\"resolve\" strategy, and it can serve as an easy-to-understand\nexample to demonstrate what a merge-strategy implementation should\nlook like.  I however doubt with improvements to the \"recursive\" and\nmore recently the \"ort\" strategy, I do not know how much \"real\" use\nthere is to it.  I even suspect that the users do not mind if a\nplatform does not ship this strategy by default if it has so much\nproblem running a shell script.\n\nBy rewriting it to C, we would lose an easy-to-understand example\nthat the users can easily run to see how it works, but what we gain\nin exchange is not clear, at least to me.\n"},{"id":"461174","messageId":"xmqqwnbc86c5.fsf@gitster.g","threadId":"53755","inReplyTo":"20220809185429.20098-12-alban.gruin@gmail.com","subject":"Re: [PATCH v8 11/14] merge: use the \"resolve\" strategy without forking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-13T16:18:50Z","receivedAt":"2022-08-13T16:18:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> This teaches `git merge' to invoke the \"resolve\" strategy with a\n> function call instead of forking.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  builtin/merge.c | 4 ++++\n>  1 file changed, 4 insertions(+)\n>\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index f7c92c0e64..0ab2993ab2 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -44,6 +44,7 @@\n>  #include \"commit-reach.h\"\n>  #include \"wt-status.h\"\n>  #include \"commit-graph.h\"\n> +#include \"merge-strategies.h\"\n>  \n>  #define DEFAULT_TWOHEAD (1<<0)\n>  #define DEFAULT_OCTOPUS (1<<1)\n> @@ -774,6 +775,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n>  \t\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n>  \t\t\tdie(_(\"unable to write %s\"), get_index_file());\n>  \t\treturn clean ? 0 : 1;\n> +\t} else if (!strcmp(strategy, \"resolve\")) {\n> +\t\treturn merge_strategies_resolve(the_repository, common,\n> +\t\t\t\t\t\thead_arg, remoteheads);\n>  \t} else {\n>  \t\treturn try_merge_command(the_repository,\n>  \t\t\t\t\t strategy, xopts_nr, xopts,\n\nThis is another thing that probably hurts the overall project more\nthan it helps, I am afraid.\n\nRecall the recent effort by Elijah's en/merge-restore-to-pristine topic cf.\nhttps://lore.kernel.org/git/pull.1231.v5.git.1658541198.gitgitgadget@gmail.com/\n\nThere are different failure modes of merge strategy backends, and\nthe \"git merge\" command that drives them must be prepared to handle\nvarious failures from them.  It is one selling point of \"git merge\"\nthat there is a codepath that lets you use your own merge strategy\nbackend.\n\nBefore this series, we had recursive and ort backends that are\ninternally called without going through try_merge_command()\ncodepath, and resolve and octopus covered the more general codepath,\nthe same one that is used by external third-party strategy backends,\nand we had test coverage for all.\n\nAs I said earlier, as the \"ort\" strategy got more mature and\nperformant, the simpler \"resolve\" may have outlived the value we get\nout of its use in the real world (read: what's the last time you ran\n\"git merge\" with th e\"-s resolve\" option?).  So at this point, the\nvalue of having tests of \"-s resolve\" in our test suite mostly does\nnot come from the fact that we are keeping \"resolve\" alive.  It\ncomes from the fact that we are making sure that the codepath to\ndrive external merge strategy does not regress.  While it moves to\ninternally call resolve and octopus, I do not think this series\ncompensates the loss of test coverage by adding tests to drive a\ncustom merge strategy.\n\nA possible correction may be to _add_ a new merge strategy written\nin C that implements the same algorithm \"resolve\" uses, give it a\ndifferent name, say \"c-resolve\", and call it internally instead of\nspawning.  And keep \"resolve\" instead of replacing it with\n\"c-resolve\".  You can duplicate the tests we have for \"-s resolve\"\nso that the new \"-s c-resolve\" codepath gets tested to the same\ndegree.  Then we will not lose the value \"resolve\" has, which is to\nserve as a testbed for external merge strategy.\n\nBut as I said already, I suspect that \"-s resolve\" is not of much\nuse in the real world, not because it is not implemented in C but\nbecause there is a generally better alternative.  It makes us wonder\nif we are making good use of our engineering effort by giving yet\nanother strategy, \"-s c-resolve\", to the users.\n\nIOW, I am not sure there is value in rewriting resolve in C (except\nfor educational value for the developer who does the task, that is),\nand it is doubly dubious to call it internally instead of spawning\nit as an external command.\n\nSo, I dunno.  I think between octopus and resolve, the former might\nstill be used and it might make sense to have a more \"performant\"\nversion of it (there is no strong reason why it needs to use the\nsame resolve backend for repeated pairwise merges it does---it could\njust call into recursive or ort machinery instead if the resolve\nmachinery is more cumbersome to use) by rewriting it in C.  But\nrewriting \"resolve\" in C to call it internally looks to me a\nregression overall to the value \"resolve\" gives to this project.\nStopping at rewriting it in C but still calling it externally might\nmake it more acceptable, though.\n\nThanks.\n\n"},{"id":"461278","messageId":"qs23r0n8-9r24-6095-3n9n-9131s69974p1@tzk.qr","threadId":"53755","inReplyTo":"xmqqilmzkd7p.fsf@gitster.g","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-08-16T12:09:23Z","receivedAt":"2022-08-16T12:14:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 10 Aug 2022, Junio C Hamano wrote:\n\n> While I agree that it would make sense to rewrite some strategies in\n> C, I do not quite see the point of redoing this particular one.  Its\n> simplicity is one of the only few remaining shining points in the\n> \"resolve\" strategy, and it can serve as an easy-to-understand\n> example to demonstrate what a merge-strategy implementation should\n> look like.\n\nI am sure we can do much better than\n\n\thttps://github.com/git/git/blob/v2.37.2/git-merge-resolve.sh\n\nwhen it comes to demonstrating a script to implement a custom merge\nstrategy. The really nice thing about a custom merge strategy, after all,\nis that you can forgo pretty much all error handling and command-line\nparsing because you know precisely how you are going to use it.\n\n> I however doubt with improvements to the \"recursive\" and more recently\n> the \"ort\" strategy, I do not know how much \"real\" use there is to it.  I\n> even suspect that the users do not mind if a platform does not ship this\n> strategy by default if it has so much problem running a shell script.\n>\n> By rewriting it to C, we would lose an easy-to-understand example that\n> the users can easily run to see how it works, but what we gain in\n> exchange is not clear, at least to me.\n\nWe reduce Git's reliance on POSIX shell scripting, we reduce the number of\nprogramming languages contributors need to be familiar with, we open up to\ncode coverage/static analysis tools that handle C but not shell scripts,\njust to name a few.\n\nIf you want to have an easy example of a custom merge strategy, then let's\nhave that easy example. `git-merge-resolve.sh` ain't that example.\n\nIt would be a different matter if you had commented about\n`git-merge-ours.sh`:\nhttps://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\nThat _was_ a simple and easy example.\n\nI would also have understood a lament about the absence of any good\nexample in https://git-scm.com/docs/git-merge#_merge_strategies to help\nusers develop their own custom merge strategies.\n\nI'm all in favor of adding such a good example there, but there is no\nreason to hold back `git merge-resolve` from being implemented in C.\n\nCiao,\nDscho\n"},{"id":"461279","messageId":"128n8n08-23ss-pnsr-n910-o39nr32q42n5@tzk.qr","threadId":"53755","inReplyTo":"08ea1eec-58fb-cbfa-d405-0d4159c99515@gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-08-16T12:17:51Z","receivedAt":"2022-08-16T12:19:13Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Wed, 10 Aug 2022, Phillip Wood wrote:\n\n> On 09/08/2022 19:54, Alban Gruin wrote:\n> > This rewrites `git merge-resolve' from shell to C.  As for `git\n> > merge-one-file', this port is not completely straightforward and removes\n> > calls to external processes to avoid reading and writing the index over\n> > and over again.\n> >\n> >   - The call to `update-index -q --refresh' is replaced by a call to\n> >     refresh_index().\n> >\n> >   - The call to `read-tree' is replaced by a call to unpack_trees() (and\n> >     all the setup needed).\n> >\n> >   - The call to `write-tree' is replaced by a call to\n> >     cache_tree_update().  This call is wrapped in a new function,\n> >     write_tree().  It is made to mimick write_index_as_tree() with\n> >     WRITE_TREE_SILENT flag, but without locking the index; this is taken\n> >     care directly in merge_strategies_resolve().\n> >\n> >   - The call to `diff-index ...' is replaced by a call to\n> >     repo_index_has_changes().\n> >\n> >   - The call to `merge-index', needed to invoke `git merge-one-file', is\n> >     replaced by a call to the new merge_all_index() function.\n> >\n> > The index is read in cmd_merge_resolve(), and is wrote back by\n> > merge_strategies_resolve().  This is to accomodate future applications:\n> > in `git-merge', the index has already been read when the merge strategy\n> > is called, so it would be redundant to read it again when the builtin\n> > will be able to use merge_strategies_resolve() directly.\n> >\n> > The parameters of merge_strategies_resolve() will be surprising at first\n> > glance: why using a commit list for `bases' and `remote', where we could\n> > use an oid array, and a pointer to an oid?  Because, in a later commit,\n> > try_merge_strategy() will be able to call merge_strategies_resolve()\n> > directly, and it already uses a commit list for `bases' (`common') and\n> > `remote' (`remoteheads'), and a string for `head_arg'.  To reduce\n> > frictions later, merge_strategies_resolve() takes the same types of\n> > parameters.\n>\n> git-merge-resolve will happily merge three trees, unfortunately using\n> lists of commits will break that.\n\nBut isn't `merge-resolve` specifically implemented as a merge strategy? I\ndo not see any contract in Git's documentation that commits to supporting\ndirect calls to the implementation detail that is `git merge-resolve`:\n\n\t$ man git-merge-resolve\n\tNo manual entry for git-merge-resolve\n\n> > merge_strategies_resolve() locks the index only once, at the beginning\n> > of the merge, and releases it when the merge has been completed.\n> >\n> > Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> > ---\n> > diff --git a/builtin/merge-resolve.c b/builtin/merge-resolve.c\n> > new file mode 100644\n> > index 0000000000..a51158ebf8\n> > --- /dev/null\n> > +++ b/builtin/merge-resolve.c\n> > @@ -0,0 +1,63 @@\n> > +/*\n> > + * Builtin \"git merge-resolve\"\n> > + *\n> > + * Copyright (c) 2020 Alban Gruin\n> > + *\n> > + * Based on git-merge-resolve.sh, written by Linus Torvalds and Junio C\n> > + * Hamano.\n> > + *\n> > + * Resolve two trees, using enhanced multi-base read-tree.\n> > + */\n> > +\n> > +#include \"cache.h\"\n> > +#include \"builtin.h\"\n> > +#include \"merge-strategies.h\"\n> > +\n> > +static const char builtin_merge_resolve_usage[] =\n> > +\t\"git merge-resolve <bases>... -- <head> <remote>\";\n> > +\n> > +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n> > +{\n> > +\tint i, sep_seen = 0;\n> > +\tconst char *head = NULL;\n> > +\tstruct commit_list *bases = NULL, *remote = NULL;\n> > +\tstruct commit_list **next_base = &bases;\n> > +\tstruct repository *r = the_repository;\n> > +\n> > +\tif (argc < 5)\n> > +\t\tusage(builtin_merge_resolve_usage);\n>\n> I think it would be better to call parse_options() and then check argc. That\n> would give better error messages for unknown options and supports '-h' for\n> free.\n\nAgain, we are talking about a merge strategy, a program that is not meant\nto be called directly by the user. Why should we complicate the code by\nusing the `parse_options` machinery?\n\n> I think we also need to call git_config(). I see that read-tree respects\n> submodule.recurse so I think we need the same here. I suspect we should\n> also be reading the merge config to respect merge.conflictStyle.\n\nValid concerns. Extra brownie points if you can provide a simple test case\nthat demonstrates the current behavior.\n\n> > +\n> > +\tif (repo_index_has_changes(r, head_tree, &sb)) {\n> > +\t\terror(_(\"Your local changes to the following files \"\n> > +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n> > +\t\t      sb.buf);\n>\n> This matches the script but I wonder why that did not check for unstaged\n> changes.\n\nAny deviations from the scripted behavior should be done on top of this\npatch series, unless the deviations make the conversion substantially\ncleaner.\n\nThanks,\nDscho\n"},{"id":"461282","messageId":"ae2a2c1c-e592-16d4-aa50-a89cc7a2d31c@gmail.com","threadId":"53755","inReplyTo":"128n8n08-23ss-pnsr-n910-o39nr32q42n5@tzk.qr","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2022-08-16T14:02:20Z","receivedAt":"2022-08-16T14:02:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"\n\nOn 16/08/2022 13:17, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Wed, 10 Aug 2022, Phillip Wood wrote:\n> \n>> On 09/08/2022 19:54, Alban Gruin wrote:\n>>> This rewrites `git merge-resolve' from shell to C.  As for `git\n>>> merge-one-file', this port is not completely straightforward and removes\n>>> calls to external processes to avoid reading and writing the index over\n>>> and over again.\n>>>\n>>>    - The call to `update-index -q --refresh' is replaced by a call to\n>>>      refresh_index().\n>>>\n>>>    - The call to `read-tree' is replaced by a call to unpack_trees() (and\n>>>      all the setup needed).\n>>>\n>>>    - The call to `write-tree' is replaced by a call to\n>>>      cache_tree_update().  This call is wrapped in a new function,\n>>>      write_tree().  It is made to mimick write_index_as_tree() with\n>>>      WRITE_TREE_SILENT flag, but without locking the index; this is taken\n>>>      care directly in merge_strategies_resolve().\n>>>\n>>>    - The call to `diff-index ...' is replaced by a call to\n>>>      repo_index_has_changes().\n>>>\n>>>    - The call to `merge-index', needed to invoke `git merge-one-file', is\n>>>      replaced by a call to the new merge_all_index() function.\n>>>\n>>> The index is read in cmd_merge_resolve(), and is wrote back by\n>>> merge_strategies_resolve().  This is to accomodate future applications:\n>>> in `git-merge', the index has already been read when the merge strategy\n>>> is called, so it would be redundant to read it again when the builtin\n>>> will be able to use merge_strategies_resolve() directly.\n>>>\n>>> The parameters of merge_strategies_resolve() will be surprising at first\n>>> glance: why using a commit list for `bases' and `remote', where we could\n>>> use an oid array, and a pointer to an oid?  Because, in a later commit,\n>>> try_merge_strategy() will be able to call merge_strategies_resolve()\n>>> directly, and it already uses a commit list for `bases' (`common') and\n>>> `remote' (`remoteheads'), and a string for `head_arg'.  To reduce\n>>> frictions later, merge_strategies_resolve() takes the same types of\n>>> parameters.\n>>\n>> git-merge-resolve will happily merge three trees, unfortunately using\n>> lists of commits will break that.\n> \n> But isn't `merge-resolve` specifically implemented as a merge strategy? I\n> do not see any contract in Git's documentation that commits to supporting\n> direct calls to the implementation detail that is `git merge-resolve`:\n> \n> \t$ man git-merge-resolve\n> \tNo manual entry for git-merge-resolve\n\nI've certainly got scripts that call \"git merge-recursive\" with a \nmixture of commits and trees (it's kind of doing an cherry-pick), it \nwouldn't surprise me if someone was doing something weird with \nmerge-resolve.\n>>> +int cmd_merge_resolve(int argc, const char **argv, const char *prefix)\n>>> +{\n>>> +\tint i, sep_seen = 0;\n>>> +\tconst char *head = NULL;\n>>> +\tstruct commit_list *bases = NULL, *remote = NULL;\n>>> +\tstruct commit_list **next_base = &bases;\n>>> +\tstruct repository *r = the_repository;\n>>> +\n>>> +\tif (argc < 5)\n>>> +\t\tusage(builtin_merge_resolve_usage);\n>>\n>> I think it would be better to call parse_options() and then check argc. That\n>> would give better error messages for unknown options and supports '-h' for\n>> free.\n> \n> Again, we are talking about a merge strategy, a program that is not meant\n> to be called directly by the user. Why should we complicate the code by\n> using the `parse_options` machinery?\n\nI thought it would simplify the implementation of '-h' below. However as \nthe script does not support '-h' we should perhaps drop support for that \nand the usage() call if we want a strictly equivalent conversion.\n\n>> I think we also need to call git_config(). I see that read-tree respects\n>> submodule.recurse so I think we need the same here. I suspect we should\n>> also be reading the merge config to respect merge.conflictStyle.\n> \n> Valid concerns. Extra brownie points if you can provide a simple test case\n> that demonstrates the current behavior.\n\nI'll add it to my todo list.\n\n>>> +\n>>> +\tif (repo_index_has_changes(r, head_tree, &sb)) {\n>>> +\t\terror(_(\"Your local changes to the following files \"\n>>> +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n>>> +\t\t      sb.buf);\n>>\n>> This matches the script but I wonder why that did not check for unstaged\n>> changes.\n> \n> Any deviations from the scripted behavior should be done on top of this\n> patch series, unless the deviations make the conversion substantially\n> cleaner.\n\nI agree. Having thought some more I suspect it is relying on \nunpack_trees() to error out if there are unstaged changes.\n\nBest Wishes\n\nPhillip\n\n> Thanks,\n> Dscho\n"},{"id":"461308","messageId":"xmqqedxgt1zx.fsf@gitster.g","threadId":"53755","inReplyTo":"qs23r0n8-9r24-6095-3n9n-9131s69974p1@tzk.qr","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-16T19:36:02Z","receivedAt":"2022-08-16T19:36:12Z","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'm all in favor of adding such a good example there, but there is no\n> reason to hold back `git merge-resolve` from being implemented in C.\n\nYou did not address the primary point, i.e. why the particular\nchange is a bad one.  Sure, you lost a scripted porcelain or two\nthat are not used much, but in exchange for what?  That is _the_\nissue and you skirt around it.\n\nThe series makes us lose all strategies that are actively tested\nthat are spawned as a subprocess, which is the way all third-party\nstrategies will be used.  After this, we have less test coverage of\nthe codepaths we care about, which is *not* a scripted \"resolve\"\nstrategy, but the code that runs third-party strategies as\nexternals.\n"},{"id":"461333","messageId":"220817.86lernaa9i.gmgdl@evledraar.gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-4-alban.gruin@gmail.com","subject":"Re: [PATCH v8 03/14] merge-index: libify merge_one_path() and merge_all()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-17T02:10:47Z","receivedAt":"2022-08-17T02:12:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Aug 09 2022, Alban Gruin wrote:\n\n[Re-arranged]\n\nRather than changing this behavior:\n\n> diff --git a/t/t7607-merge-state.sh b/t/t7607-merge-state.sh\n> index 89a62ac53b..96befa5b80 100755\n> --- a/t/t7607-merge-state.sh\n> +++ b/t/t7607-merge-state.sh\n> @@ -20,7 +20,7 @@ test_expect_success 'Ensure we restore original state if no merge strategy handl\n>  \t# just hit conflicts, it completely fails and says that it cannot\n>  \t# handle this type of merge.\n>  \ttest_expect_code 2 git merge branch2 branch3 >output 2>&1 &&\n> -\tgrep \"fatal: merge program failed\" output &&\n> +\tgrep \"error: merge program failed\" output &&\n>  \tgrep \"Should not be doing an octopus\" output &&\n\nIn this (or some of it?):\n\n> [...]\n>  \t# Make sure we did not leave stray changes around when no appropriate\n> [...]\n> -\tif (run_command_v_opt(arguments, 0)) {\n> -\t\tif (one_shot)\n> -\t\t\terr++;\n> -\t\telse {\n> -\t\t\tif (!quiet)\n> -\t\t\t\tdie(\"merge program failed\");\n> -\t\t\texit(1);\n> [...]\n> +\t\t\tif (!quiet && !oneshot)\n> +\t\t\t\terror(_(\"merge program failed\"));\n> +\t\t\treturn 1;\n> [...]\n> +\t\tif (err && !oneshot) {\n> +\t\t\tif (!quiet)\n> +\t\t\t\terror(_(\"merge program failed\"));\n> +\t\t\treturn 1;\n> +\t\t}\n> +\t}\n> +\n> +\tif (err && !quiet)\n> +\t\terror(_(\"merge program failed\"));\n\nShould we not be using die_message() here instead?\n"},{"id":"461334","messageId":"220817.86h72ba9uh.gmgdl@evledraar.gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-9-alban.gruin@gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-17T02:16:09Z","receivedAt":"2022-08-17T02:21:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Aug 09 2022, Alban Gruin wrote:\n\nI think the rest of this series has been careful to keep output as-is\n(in some cases arguably to a fault, e.g. carrying forward two\n\"gettextln\" invocations as two puts(), which we should almost definitely\nfold into one string).\n\nBut here:\n\n> -    gettextln \"Error: Your local changes to the following files would be overwritten by merge\"\n> [...]\n> +\t\terror(_(\"Your local changes to the following files \"\n> +\t\t\t\"would be overwritten by merge:\\n  %s\"),\n> +\t\t      sb.buf);\n\nWe introduce a subtle behavior change, we used to say \"Error:\", but now\nit's \"error:\". Also since a1fd2cf8cd6 (i18n: mark message helpers prefix\nfor translation, 2022-06-21) the interaction with how \"error: \" is\ntranslated is different, but let's leave that aside.\n\nNow, I think the change probably makes sense & isn't risky, but perhaps\nnote it in a commit message, or even precede it with a commit to\ns/Error/error/g in the *.sh code before the migration?\n\nAlso, if we *are* changing it while we're at it let's also s/Your/your/,\nno? I see some other uses in other pre-existing merge*.c files, so maybe\nthe unusual casing is magical.\n"},{"id":"461356","messageId":"848p4p89-2219-7874-ss50-2o0rp4r02902@tzk.qr","threadId":"53755","inReplyTo":"xmqqedxgt1zx.fsf@gitster.g","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-08-17T09:42:48Z","receivedAt":"2022-08-17T09:43:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Tue, 16 Aug 2022, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > I'm all in favor of adding such a good example there, but there is no\n> > reason to hold back `git merge-resolve` from being implemented in C.\n>\n> You did not address the primary point, i.e. why the particular\n> change is a bad one.  Sure, you lost a scripted porcelain or two\n> that are not used much, but in exchange for what?  That is _the_\n> issue and you skirt around it.\n\nIn exchange for what I mentioned already in\nhttps://lore.kernel.org/git/qs23r0n8-9r24-6095-3n9n-9131s69974p1@tzk.qr/,\ni.e. in the part you deleted from the quoted mail:\n\n\tWe reduce Git's reliance on POSIX shell scripting, we reduce the\n\tnumber of programming languages contributors need to be familiar\n\twith, we open up to code coverage/static analysis tools that\n\thandle C but not shell scripts, just to name a few.\n\nTo reiterate why reducing the reliance on POSIX shell scripting is a good\nthing:\n\n- we pay a steep price in the form of performance issues (you will recall\n  that merely rewriting the `rebase -i` engine in C and nothing else\n  improved the overall run time of p3404 5x on Windows, 4x on macOS and\n  still 3.5x on Linux, see\n  https://lore.kernel.org/git/cover.1483370556.git.johannes.schindelin@gmx.de/)\n\n  Yes, Linux sees such an incredible performance boost. Surprising, right?\n\n- on Windows, even aside from the performance problems (which I deem\n  reason enough on their own to aim for Git being implemented purely in\n  C), users run into issues where anti-malware simply blocks shell\n  scripts, sometimes even quarantines entire parts of Git for Windows.\n\n- have you ever attempted to debug a Git invocation that involves spawning\n  a shell script that in turn spawns the failing Git command, using `gdb`?\n  I have. It ain't pretty. And you know that there are easier ways to\n  abuse and deter new contributors than to ask them to do the same. In\n  particular when large amounts of data have to be passed between those\n  processes, typically via `stdio`.\n\n- show me the equivalent of CodeQL/Coverity for POSIX shell scripting? ;-)\n\n- portability issues dictate that we're not just using your grand father's\n  POSIX shell scripting, but that we limit it to a subset that is opaque\n  to developers unfamiliar with Git project.\n\n- as a consequence, our shell scripts are highly opinionated, often using\n  unintuitive idioms such as `&&` chains instead of `set -e`, which makes\n  them unsuitable as examples how to script Git for regular users.\n\n- a decreasing number of software developers is familiar with the\n  intricacies of that language, leaving us with tech debt.\n\nIn short, there is not a single shred of doubt in my mind that avoiding\nshell scripted parts in Git is a really good goal to have for this\nproject.\n\n> The series makes us lose all strategies that are actively tested\n> that are spawned as a subprocess, which is the way all third-party\n> strategies will be used.\n\nThen have that even-simpler-than `git-merge-resolve.sh` example be tested\nas part of the test suite. That's what the test suite is for.\n\n> After this, we have less test coverage of the codepaths we care about,\n> which is *not* a scripted \"resolve\" strategy, but the code that runs\n> third-party strategies as externals.\n\nIt is better to leave the responsibility of test coverage to the test\nsuite, avoiding to ship the corresponding support code to users.\n\ntl;dr your concerns are easy to address, without having to incur the price\nof keeping parts of Git implemented in shell.\n\nCiao,\nDscho\n"},{"id":"461380","messageId":"CABPp-BGSFYWvA5HktLf33=w7JB95iDLDNoE0gdA3oUtb+qYoQQ@mail.gmail.com","threadId":"53755","inReplyTo":"848p4p89-2219-7874-ss50-2o0rp4r02902@tzk.qr","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-08-17T19:06:16Z","receivedAt":"2022-08-17T19:06:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Dscho,\n\nI share some of Junio's concerns, and feel your response is addressing\na tangent but not the actual issues.  Perhaps I can try to explain why\nfrom a slightly different perspective...\n\nOn Wed, Aug 17, 2022 at 2:51 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Junio,\n>\n> On Tue, 16 Aug 2022, Junio C Hamano wrote:\n>\n> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> >\n> > > I'm all in favor of adding such a good example there, but there is no\n> > > reason to hold back `git merge-resolve` from being implemented in C.\n> >\n> > You did not address the primary point, i.e. why the particular\n> > change is a bad one.  Sure, you lost a scripted porcelain or two\n> > that are not used much, but in exchange for what?  That is _the_\n> > issue and you skirt around it.\n>\n> In exchange for what I mentioned already in\n> https://lore.kernel.org/git/qs23r0n8-9r24-6095-3n9n-9131s69974p1@tzk.qr/,\n> i.e. in the part you deleted from the quoted mail:\n>\n>         We reduce Git's reliance on POSIX shell scripting, we reduce the\n>         number of programming languages contributors need to be familiar\n>         with, we open up to code coverage/static analysis tools that\n>         handle C but not shell scripts, just to name a few.\n>\n> To reiterate why reducing the reliance on POSIX shell scripting is a good\n> thing:\n>\n> - we pay a steep price in the form of performance issues (you will recall\n>   that merely rewriting the `rebase -i` engine in C and nothing else\n>   improved the overall run time of p3404 5x on Windows, 4x on macOS and\n>   still 3.5x on Linux, see\n>   https://lore.kernel.org/git/cover.1483370556.git.johannes.schindelin@gmx.de/)\n>\n>   Yes, Linux sees such an incredible performance boost. Surprising, right?\n\nSure, scripts are slow *if* run.  Junio asked explicitly about that\n\"if\" part, which you seem to be overlooking, and thus you are\nanswering a different question.\n\nIs anyone, anywhere, ever running `-s resolve`?  Junio is doubting it,\nand given that even some Git developers who were unaware of its\nexistence[1], I have to wonder too.\n\n[1] https://public-inbox.org/git/kl6l7d58k535.fsf@chooglen-macbookpro.roam.corp.google.com/,\nlook for \"today I learned...\"\n\n> - on Windows, even aside from the performance problems (which I deem\n>   reason enough on their own to aim for Git being implemented purely in\n>   C), users run into issues where anti-malware simply blocks shell\n>   scripts, sometimes even quarantines entire parts of Git for Windows.\n\nI'm not sure I'm following.  If users do attempt to run `git\n{merge,rebase,cherry-pick,revert} --strategy resolve`, then\nanti-malware disables other parts of Git for Windows?  Or is the mere\npresence of git-merge-resolve enough to trigger such problems?  If the\nlatter, then I could agree that's really problematic and worth\naddressing.  If the former, we may be back to that all important \"if\".\nBut I'm not sure it's one of those two; could you clarify a bit here?\n\n> - have you ever attempted to debug a Git invocation that involves spawning\n>   a shell script that in turn spawns the failing Git command, using `gdb`?\n>   I have. It ain't pretty. And you know that there are easier ways to\n>   abuse and deter new contributors than to ask them to do the same. In\n>   particular when large amounts of data have to be passed between those\n>   processes, typically via `stdio`.\n\nYes, that's very painful.  It's annoyed me many times.  It's a\nproblem, *if* you need to debug a script.  But again, you seem to be\npresuming that git-merge-resolve is in use, which dodges the very\nquestion Junio was asking.  Is it in use?\n\n> - show me the equivalent of CodeQL/Coverity for POSIX shell scripting? ;-)\n>\n> - portability issues dictate that we're not just using your grand father's\n>   POSIX shell scripting, but that we limit it to a subset that is opaque\n>   to developers unfamiliar with Git project.\n>\n> - as a consequence, our shell scripts are highly opinionated, often using\n>   unintuitive idioms such as `&&` chains instead of `set -e`, which makes\n>   them unsuitable as examples how to script Git for regular users.\n>\n> - a decreasing number of software developers is familiar with the\n>   intricacies of that language, leaving us with tech debt.\n\nYes, these are real issues for code written in shell being actively\ndeveloped and maintained, yes.  (shellcheck might help as a\nCodeQL/Coverity-like thing for shell.)\n\nHowever, that doesn't really apply here.  There have literally only\nbeen two commits to git-merge-resolve.sh in the last decade, one from\nme that copied a few lines verbatim from git-merge-octopus.sh, and the\nother was a single character change 5 years ago.\n\n> In short, there is not a single shred of doubt in my mind that avoiding\n> shell scripted parts in Git is a really good goal to have for this\n> project.\n\nI think it's a good goal in general, especially for anything heavily\nused.  I share Junio's concern about this one in particular.  I'm not\nsure this script is even being used directly, the maintenance burden\nfor it is essentially zero, and the script does have both educational\nand testing value.\n\n> > The series makes us lose all strategies that are actively tested\n> > that are spawned as a subprocess, which is the way all third-party\n> > strategies will be used.\n>\n> Then have that even-simpler-than `git-merge-resolve.sh` example be tested\n> as part of the test suite. That's what the test suite is for.\n\nThat simpler thing being a resurrection of git-merge-ours.sh from\na00a42ae33708caa742d9e9fbf10692cfa42f032^ ?\n\nThat would test that we shell out to another strategy.  But it\nwouldn't really test as many of the cases in builtin/merge.c for\ndealing with external strategies.  `-s resolve` can fail on\n\"interesting\" changes, after making changes to the working tree and\nindex, and builtin/merge.c is expected to handle that -- using a\nsimpler example would lose that important testing.  (I kinda think\nit's a bug that it doesn't clean up after itself and that we made\nbuiltin/merge.c do the cleanup, but backward compatibility suggests we\nat least need some way to keep testing that we handle that.)  We would\nalso need to be careful about testing the \"preferred\" strategy when\nthe user asks for multiple strategies, another thing covered in our\ntestsuite (though using two builtins might be good enough for that).\nI'd have to look over the testsuite to check and see if there are\nother important properties being tested too; -s resolve has been used\nin a few dozen places.\n\n> > After this, we have less test coverage of the codepaths we care about,\n> > which is *not* a scripted \"resolve\" strategy, but the code that runs\n> > third-party strategies as externals.\n>\n> It is better to leave the responsibility of test coverage to the test\n> suite, avoiding to ship the corresponding support code to users.\n>\n> tl;dr your concerns are easy to address, without having to incur the price\n> of keeping parts of Git implemented in shell.\n\nThere's also another concern you tried to address in your other email;\nlet me quote from that email here:\n\n> If you want to have an easy example of a custom merge strategy, then let's\n> have that easy example. `git-merge-resolve.sh` ain't that example.\n>\n> It would be a different matter if you had commented about\n> `git-merge-ours.sh`:\n> https://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\n> That _was_ a simple and easy example.\n\n...and it was _utterly useless_ as an example.  It only checked that\nthe user hadn't modified the index since HEAD.  It doesn't demonstrate\nanything about how to merge differing entries, since that merge\nstrategy specifically ignores changes made on the other side.  Since\nmerging differing entries is the whole point of writing a strategy, I\nsee no educational value in that particular script.\n\n`git-merge-resolve.sh` may be an imperfect example, but it's certainly\nfar superior to that.\n\n> I would also have understood a lament about the absence of any good\n> example in https://git-scm.com/docs/git-merge#_merge_strategies to help\n> users develop their own custom merge strategies.\n>\n> I'm all in favor of adding such a good example there, but there is no\n> reason to hold back `git merge-resolve` from being implemented in C.\n\nIf someone makes a better example (which I agree could be done,\nespecially if it added lots of comments about what was required and\nwhy), and ensures we keep useful test coverage (maybe using Junio's\nc-resolve suggestion in another email), then my concerns about\nreimplementing git-merge-resolve.sh in C go away.\n\nIf that happens, then I still think it's a useless exercise to do the\nreimplementation -- unless someone can provide evidence of `-s\nresolve` being in use -- but it's not a harmful exercise and wouldn't\nconcern me.\n\nIf the better example and mechanism to retain good test coverage\naren't provided, then I worry that reimplementing is a bunch of work\nfor an at best theoretical benefit, coupled with a double whammy\npractical regression.\n"},{"id":"461381","messageId":"xmqqbksivg3t.fsf@gitster.g","threadId":"53755","inReplyTo":"848p4p89-2219-7874-ss50-2o0rp4r02902@tzk.qr","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-17T19:12:54Z","receivedAt":"2022-08-17T19:13:00Z","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> To reiterate why reducing the reliance on POSIX shell scripting is a good\n> thing:\n>\n> - we pay a steep price in the form of performance issues (you will recall\n\nIrrelevant.  Who uses resolve these days?\n\n> - have you ever attempted to debug a Git invocation that involves spawning\n>   a shell script that in turn spawns the failing Git command, using `gdb`?\n\nRemember, I have been doing this longer than you have, so of course\nI have, but I do not think it is relevant.  An external program as a\nmerge strategy does not have to be written in shell, but third-party\nstrategies can be written in anything, so some who choose to do so\nmay still have to.  There is no avoiding that.\n\nWhat our contributors, new and old, need to do is to maintain the\ncodepath that spawns these third-party strategy programs working.\n\nThere were two steps I gave review messages to, and the one I had\nmore trouble with was actually not the [08/14] you are making big\nfuss about.  It was the \"we no longer spawn resolve or octopus\"\nstep(s).  If we really want to rewrite \"resolve\" in C, while I think\nthere are better ways to use our resources, rewriting it by itself\nwould not _hurt_ the project all that much, as long as we keep it an\nexternal program.\n\nAnd by \"maintain the codepath working\", we would want to catch silly\nmistakes while \"refactoring\", like the one we had when we changed\nthe underlying machinery to spawn hooks in a recent release, without\ncaring (I wouldn't say \"without knowing\"; those who did and reviewed\nthe change including me didn't even think about how the standard I/O\nstreams are seen by hook scripts and how they react to them).  Just\nlike tests around small toy sample hooks did not catch the\nregression, \"a small toy sample that is only spawned in a test piece\nor two to pretend to be a merge strategy program\" would not be a\ngood substitute for running something real.\n\n\n"},{"id":"461382","messageId":"xmqq7d36vfur.fsf@gitster.g","threadId":"53755","inReplyTo":"CABPp-BGSFYWvA5HktLf33=w7JB95iDLDNoE0gdA3oUtb+qYoQQ@mail.gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-17T19:18:20Z","receivedAt":"2022-08-17T19:18:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> There's also another concern you tried to address in your other email;\n> let me quote from that email here:\n>\n>> If you want to have an easy example of a custom merge strategy, then let's\n>> have that easy example. `git-merge-resolve.sh` ain't that example.\n>>\n>> It would be a different matter if you had commented about\n>> `git-merge-ours.sh`:\n>> https://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\n>> That _was_ a simple and easy example.\n>\n> ...and it was _utterly useless_ as an example.  It only checked that\n> the user hadn't modified the index since HEAD.  It doesn't demonstrate\n> anything about how to merge differing entries, since that merge\n> strategy specifically ignores changes made on the other side.  Since\n> merging differing entries is the whole point of writing a strategy, I\n> see no educational value in that particular script.\n>\n> `git-merge-resolve.sh` may be an imperfect example, but it's certainly\n> far superior to that.\n> ...\n> If someone makes a better example (which I agree could be done,\n> especially if it added lots of comments about what was required and\n> why), and ensures we keep useful test coverage (maybe using Junio's\n> c-resolve suggestion in another email), then my concerns about\n> reimplementing git-merge-resolve.sh in C go away.\n>\n> If that happens, then I still think it's a useless exercise to do the\n> reimplementation -- unless someone can provide evidence of `-s\n> resolve` being in use -- but it's not a harmful exercise and wouldn't\n> concern me.\n>\n> If the better example and mechanism to retain good test coverage\n> aren't provided, then I worry that reimplementing is a bunch of work\n> for an at best theoretical benefit, coupled with a double whammy\n> practical regression.\n\nAh, you said many things I wanted to say already.  Thanks.\n"},{"id":"461426","messageId":"220818.868rnlaa0h.gmgdl@evledraar.gmail.com","threadId":"53755","inReplyTo":"xmqq7d36vfur.fsf@gitster.g","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-18T14:24:38Z","receivedAt":"2022-08-18T14:42:34Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Aug 17 2022, Junio C Hamano wrote:\n\n> Elijah Newren <newren@gmail.com> writes:\n>\n>> There's also another concern you tried to address in your other email;\n>> let me quote from that email here:\n>>\n>>> If you want to have an easy example of a custom merge strategy, then let's\n>>> have that easy example. `git-merge-resolve.sh` ain't that example.\n>>>\n>>> It would be a different matter if you had commented about\n>>> `git-merge-ours.sh`:\n>>> https://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\n>>> That _was_ a simple and easy example.\n>>\n>> ...and it was _utterly useless_ as an example.  It only checked that\n>> the user hadn't modified the index since HEAD.  It doesn't demonstrate\n>> anything about how to merge differing entries, since that merge\n>> strategy specifically ignores changes made on the other side.  Since\n>> merging differing entries is the whole point of writing a strategy, I\n>> see no educational value in that particular script.\n>>\n>> `git-merge-resolve.sh` may be an imperfect example, but it's certainly\n>> far superior to that.\n>> ...\n>> If someone makes a better example (which I agree could be done,\n>> especially if it added lots of comments about what was required and\n>> why), and ensures we keep useful test coverage (maybe using Junio's\n>> c-resolve suggestion in another email), then my concerns about\n>> reimplementing git-merge-resolve.sh in C go away.\n>>\n>> If that happens, then I still think it's a useless exercise to do the\n>> reimplementation -- unless someone can provide evidence of `-s\n>> resolve` being in use -- but it's not a harmful exercise and wouldn't\n>> concern me.\n>>\n>> If the better example and mechanism to retain good test coverage\n>> aren't provided, then I worry that reimplementing is a bunch of work\n>> for an at best theoretical benefit, coupled with a double whammy\n>> practical regression.\n>\n> Ah, you said many things I wanted to say already.  Thanks.\n\nI may have missed something in this thread, but wouldn't an acceptable\nway to please everyone here be to:\n\n 1. Have git's behavior be that of the end of this series...\n 2. Add a GIT_TEST_* mode where we'll optionally invoke these \"built-in\"\n    merge strategies as commands, i.e. have them fall back to\n    \"try_merge_command()\".\n\nSo something like this on top of this series (assume my SOB etc. if this\nis acceptable). I only tested this locally, but it seems to do the right\nthing for me:\n\ndiff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh\nindex 8ebff425967..9d0f68b8147 100755\n--- a/ci/run-build-and-tests.sh\n+++ b/ci/run-build-and-tests.sh\n@@ -30,6 +30,7 @@ linux-TEST-vars)\n \texport GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\n \texport GIT_TEST_WRITE_REV_INDEX=1\n \texport GIT_TEST_CHECKOUT_WORKERS=2\n+\texport GIT_TEST_MERGE_COMMANDS=true\n \t;;\n linux-clang)\n \texport GIT_TEST_DEFAULT_HASH=sha1\ndiff --git a/sequencer.c b/sequencer.c\nindex 00a36205848..91d651f9b12 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2309,6 +2309,7 @@ static int do_pick_commit(struct repository *r,\n \t} else {\n \t\tstruct commit_list *common = NULL;\n \t\tstruct commit_list *remotes = NULL;\n+\t\tconst int test_commands = git_env_bool(\"GIT_TEST_MERGE_COMMANDS\", 0);\n \n \t\tres = write_message(msgbuf.buf, msgbuf.len,\n \t\t\t\t    git_path_merge_msg(r), 0);\n@@ -2316,10 +2317,10 @@ static int do_pick_commit(struct repository *r,\n \t\tcommit_list_insert(base, &common);\n \t\tcommit_list_insert(next, &remotes);\n \n-\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n+\t\tif (!test_commands && !strcmp(opts->strategy, \"resolve\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n-\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n+\t\t} else if (!test_commands && !strcmp(opts->strategy, \"octopus\")) {\n \t\t\trepo_read_index(r);\n \t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n \t\t} else {\n"},{"id":"461427","messageId":"220818.864jy9a9lm.gmgdl@evledraar.gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-9-alban.gruin@gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-18T14:43:20Z","receivedAt":"2022-08-18T14:51:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Aug 09 2022, Alban Gruin wrote:\n\n> +int merge_strategies_resolve(struct repository *r,\n> +\t\t\t     struct commit_list *bases, const char *head_arg,\n> +\t\t\t     struct commit_list *remote);\n\nIt would be very nice to have this prototype declared as a:\n\n\ttypedef int (*merge_strategy_fn_t)(...);\n\nOr whatever, so that when you later use this in 12/14. Then the end\nstate of this series could have this on top:\n\t\n\tdiff --git a/merge-strategies.h b/merge-strategies.h\n\tindex 8de2249ee6b..79b828105ba 100644\n\t--- a/merge-strategies.h\n\t+++ b/merge-strategies.h\n\t@@ -29,6 +29,9 @@ int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n\t int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n\t \t\t    merge_fn fn, void *data);\n\t \n\t+typedef int (*merge_strategy_fn_t)(struct repository *r,\n\t+\t\t\t     struct commit_list *bases, const char *head_arg,\n\t+\t\t\t     struct commit_list *remote);\n\t int merge_strategies_resolve(struct repository *r,\n\t \t\t\t     struct commit_list *bases, const char *head_arg,\n\t \t\t\t     struct commit_list *remote);\n\tdiff --git a/sequencer.c b/sequencer.c\n\tindex 00a36205848..d5ef12dda27 100644\n\t--- a/sequencer.c\n\t+++ b/sequencer.c\n\t@@ -2309,6 +2309,7 @@ static int do_pick_commit(struct repository *r,\n\t \t} else {\n\t \t\tstruct commit_list *common = NULL;\n\t \t\tstruct commit_list *remotes = NULL;\n\t+\t\tmerge_strategy_fn_t fn = NULL;\n\t \n\t \t\tres = write_message(msgbuf.buf, msgbuf.len,\n\t \t\t\t\t    git_path_merge_msg(r), 0);\n\t@@ -2316,12 +2317,14 @@ static int do_pick_commit(struct repository *r,\n\t \t\tcommit_list_insert(base, &common);\n\t \t\tcommit_list_insert(next, &remotes);\n\t \n\t-\t\tif (!strcmp(opts->strategy, \"resolve\")) {\n\t-\t\t\trepo_read_index(r);\n\t-\t\t\tres |= merge_strategies_resolve(r, common, oid_to_hex(&head), remotes);\n\t-\t\t} else if (!strcmp(opts->strategy, \"octopus\")) {\n\t+\t\tif (!strcmp(opts->strategy, \"resolve\"))\n\t+\t\t\tfn = merge_strategies_resolve;\n\t+\t\telse if (!strcmp(opts->strategy, \"resolve\"))\n\t+\t\t\tfn = merge_strategies_octopus;\n\t+\n\t+\t\tif (fn) {\n\t \t\t\trepo_read_index(r);\n\t-\t\t\tres |= merge_strategies_octopus(r, common, oid_to_hex(&head), remotes);\n\t+\t\t\tres |= fn(r, common, oid_to_hex(&head), remotes);\n\t \t\t} else {\n\t \t\t\tres |= try_merge_command(r, opts->strategy,\n\t \t\t\t\t\t\t opts->xopts_nr, (const char **)opts->xopts,\n\nWe could replace that if/else if with a static array, and loop over it\nto find the \"fn\" (if any), but I though it wasn't worth it just for\nthis.\n\nThis would also make my suggestion on top at\nhttps://lore.kernel.org/git/220818.868rnlaa0h.gmgdl@evledraar.gmail.com/\nnicer. I.e. we could just make that:\n\n\tif (git_env_bool(\"GIT_TEST_MERGE_COMMANDS\", 0))\n\t\tfn = NULL;\n\nAnd not need to add the \"are we in the test mode\" to the if/else if\nbranch for all of the internal strategies.\n"},{"id":"461449","messageId":"xmqqmtc1pidp.fsf@gitster.g","threadId":"53755","inReplyTo":"220818.868rnlaa0h.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-08-18T17:32:34Z","receivedAt":"2022-08-18T17:32:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> I may have missed something in this thread, but wouldn't an acceptable\n> way to please everyone here be to:\n\nWhy pile on MORE cruft on top of a needless rewrite into an internal\ncall, when it is cleaner to just get rid of the part that makes an\ninternal call?\n"},{"id":"461487","messageId":"CABPp-BEvn5ovFF8DzjVW-H9rQ-UdU56uT_dk80w9p7DHokD+rQ@mail.gmail.com","threadId":"53755","inReplyTo":"220818.868rnlaa0h.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-08-19T01:43:00Z","receivedAt":"2022-08-19T01:44:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Aug 18, 2022 at 7:42 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Wed, Aug 17 2022, Junio C Hamano wrote:\n>\n> > Elijah Newren <newren@gmail.com> writes:\n> >\n> >> There's also another concern you tried to address in your other email;\n> >> let me quote from that email here:\n> >>\n> >>> If you want to have an easy example of a custom merge strategy, then let's\n> >>> have that easy example. `git-merge-resolve.sh` ain't that example.\n> >>>\n> >>> It would be a different matter if you had commented about\n> >>> `git-merge-ours.sh`:\n> >>> https://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\n> >>> That _was_ a simple and easy example.\n> >>\n> >> ...and it was _utterly useless_ as an example.  It only checked that\n> >> the user hadn't modified the index since HEAD.  It doesn't demonstrate\n> >> anything about how to merge differing entries, since that merge\n> >> strategy specifically ignores changes made on the other side.  Since\n> >> merging differing entries is the whole point of writing a strategy, I\n> >> see no educational value in that particular script.\n> >>\n> >> `git-merge-resolve.sh` may be an imperfect example, but it's certainly\n> >> far superior to that.\n> >> ...\n> >> If someone makes a better example (which I agree could be done,\n> >> especially if it added lots of comments about what was required and\n> >> why), and ensures we keep useful test coverage (maybe using Junio's\n> >> c-resolve suggestion in another email), then my concerns about\n> >> reimplementing git-merge-resolve.sh in C go away.\n> >>\n> >> If that happens, then I still think it's a useless exercise to do the\n> >> reimplementation -- unless someone can provide evidence of `-s\n> >> resolve` being in use -- but it's not a harmful exercise and wouldn't\n> >> concern me.\n> >>\n> >> If the better example and mechanism to retain good test coverage\n> >> aren't provided, then I worry that reimplementing is a bunch of work\n> >> for an at best theoretical benefit, coupled with a double whammy\n> >> practical regression.\n> >\n> > Ah, you said many things I wanted to say already.  Thanks.\n>\n> I may have missed something in this thread, but wouldn't an acceptable\n> way to please everyone here be to:\n>\n>  1. Have git's behavior be that of the end of this series...\n>  2. Add a GIT_TEST_* mode where we'll optionally invoke these \"built-in\"\n>     merge strategies as commands, i.e. have them fall back to\n>     \"try_merge_command()\".\n\nIn the portion of the email you quoted and responded to, most of the\ntext was talking about how git-merge-resolve.sh serves an important\neducational purpose, yet you've only tried to address the testing\nissue.  I think both are important.  The easiest way to fix the\neducational shortcoming of this series is to reverse the deleting of\ngit-merge-resolve.sh, and restore the building and distribution of\ngit-merge-resolve from that script.  Unfortunately, that generates a\ncollision between both the script and the builtin being used to build\nthe same file (namely, git-merge-resolve)...which is yet another\nreason that the easiest solution available here is to just not rewrite\nthis script in C at all.\n\nThere are certainly other possible solutions to the educational issue,\nand might not even be too hard, but we'd need someone to implement one\nbefore I'd agree we found an \"acceptable way to please everyone\".  :-)\n\n> So something like this on top of this series (assume my SOB etc. if this\n> is acceptable). I only tested this locally, but it seems to do the right\n> thing for me:\n<snip patch>\n\nHow did you test?  I'm a bit confused...unless I'm misreading\nsomething, it appears to me that ci/lib.sh sets SKIP_DASHED_BUILT_INS\nunconditionally which would probably cause your proposal to break.\n"},{"id":"461489","messageId":"220819.865yip7xi0.gmgdl@evledraar.gmail.com","threadId":"53755","inReplyTo":"CABPp-BEvn5ovFF8DzjVW-H9rQ-UdU56uT_dk80w9p7DHokD+rQ@mail.gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-08-19T02:45:54Z","receivedAt":"2022-08-19T02:55:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Aug 18 2022, Elijah Newren wrote:\n\n> On Thu, Aug 18, 2022 at 7:42 AM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>>\n>> On Wed, Aug 17 2022, Junio C Hamano wrote:\n>>\n>> > Elijah Newren <newren@gmail.com> writes:\n>> >\n>> >> There's also another concern you tried to address in your other email;\n>> >> let me quote from that email here:\n>> >>\n>> >>> If you want to have an easy example of a custom merge strategy, then let's\n>> >>> have that easy example. `git-merge-resolve.sh` ain't that example.\n>> >>>\n>> >>> It would be a different matter if you had commented about\n>> >>> `git-merge-ours.sh`:\n>> >>> https://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\n>> >>> That _was_ a simple and easy example.\n>> >>\n>> >> ...and it was _utterly useless_ as an example.  It only checked that\n>> >> the user hadn't modified the index since HEAD.  It doesn't demonstrate\n>> >> anything about how to merge differing entries, since that merge\n>> >> strategy specifically ignores changes made on the other side.  Since\n>> >> merging differing entries is the whole point of writing a strategy, I\n>> >> see no educational value in that particular script.\n>> >>\n>> >> `git-merge-resolve.sh` may be an imperfect example, but it's certainly\n>> >> far superior to that.\n>> >> ...\n>> >> If someone makes a better example (which I agree could be done,\n>> >> especially if it added lots of comments about what was required and\n>> >> why), and ensures we keep useful test coverage (maybe using Junio's\n>> >> c-resolve suggestion in another email), then my concerns about\n>> >> reimplementing git-merge-resolve.sh in C go away.\n>> >>\n>> >> If that happens, then I still think it's a useless exercise to do the\n>> >> reimplementation -- unless someone can provide evidence of `-s\n>> >> resolve` being in use -- but it's not a harmful exercise and wouldn't\n>> >> concern me.\n>> >>\n>> >> If the better example and mechanism to retain good test coverage\n>> >> aren't provided, then I worry that reimplementing is a bunch of work\n>> >> for an at best theoretical benefit, coupled with a double whammy\n>> >> practical regression.\n>> >\n>> > Ah, you said many things I wanted to say already.  Thanks.\n>>\n>> I may have missed something in this thread, but wouldn't an acceptable\n>> way to please everyone here be to:\n>>\n>>  1. Have git's behavior be that of the end of this series...\n>>  2. Add a GIT_TEST_* mode where we'll optionally invoke these \"built-in\"\n>>     merge strategies as commands, i.e. have them fall back to\n>>     \"try_merge_command()\".\n>\n> In the portion of the email you quoted and responded to, most of the\n> text was talking about how git-merge-resolve.sh serves an important\n> educational purpose, yet you've only tried to address the testing\n> issue.  I think both are important.\n\n*Nod*, I meant (but should have said) \"on the topic of the test\n coverage\"...\n\n> The easiest way to fix the\n> educational shortcoming of this series is to reverse the deleting of\n> git-merge-resolve.sh, and restore the building and distribution of\n> git-merge-resolve from that script.  Unfortunately, that generates a\n> collision between both the script and the builtin being used to build\n> the same file (namely, git-merge-resolve)...\n\nI'd think if we were shipping it as an example we could give it a\ndifferent name, or not install it as an executable, but in the \"shared\"\npart (along with the README etc.).\n\nOr keep it in-tree in contrib, but we did try that sort of thing before\nwith 49eb8d39c78 (Remove contrib/examples/*, 2018-03-25) :)\n\nI think the best way forward is to just note in the documentation some\nexamples of how to write a merge driver, either by linking to an older\nversion of the script, or quoting it inline.\n\n> which is yet another\n> reason that the easiest solution available here is to just not rewrite\n> this script in C at all.\n\nI think there's bigger benefits to moving more things to C & built-ins,\nso I'd prefer to see some version of this where what we do by default is\nto call this C code (or similar), and not as a sub-process.\n\n> There are certainly other possible solutions to the educational issue,\n> and might not even be too hard, but we'd need someone to implement one\n> before I'd agree we found an \"acceptable way to please everyone\".  :-)\n\n*nod*\n\n>> So something like this on top of this series (assume my SOB etc. if this\n>> is acceptable). I only tested this locally, but it seems to do the right\n>> thing for me:\n> <snip patch>\n>\n> How did you test?  I'm a bit confused...unless I'm misreading\n> something, it appears to me that ci/lib.sh sets SKIP_DASHED_BUILT_INS\n> unconditionally which would probably cause your proposal to break.\n\nAdmittedly not very thoroughly, but I'm fairly sure it does the right\nthing when it comes to this, and SKIP_DASHED_BUILT_INS doesn't enter\ninto it (and all my local builds use SKIP_DASHED_BUILT_INS=Y).\n\nThe try_merge_command() invokes merge-what-ever, and does a\nrun_command_v_opt(args.v, RUN_GIT_CMD). At that point we'll invoke a\n\"git merge-what-ever\", i.e. we don't need a \"git-merge-what-ever\" binary\nto exist.\n\nThis is what we do in general when git is invoking itself, and we'd need\nto go out of our way to have it not work in this case (i.e. build it as\na stand-alone program, like git-http-fetch, and not as a built-in).\n"},{"id":"461500","messageId":"CABPp-BGmukb=C_wzg8774hYRRobGmNJybzh2kCSuLhqV+m4GqA@mail.gmail.com","threadId":"53755","inReplyTo":"220819.865yip7xi0.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v8 08/14] merge-resolve: rewrite in C","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2022-08-19T04:27:03Z","receivedAt":"2022-08-19T04:28:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Aug 18, 2022 at 7:55 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Thu, Aug 18 2022, Elijah Newren wrote:\n>\n> > On Thu, Aug 18, 2022 at 7:42 AM Ævar Arnfjörð Bjarmason\n> > <avarab@gmail.com> wrote:\n> >>\n> >> On Wed, Aug 17 2022, Junio C Hamano wrote:\n> >>\n> >> > Elijah Newren <newren@gmail.com> writes:\n> >> >\n> >> >> There's also another concern you tried to address in your other email;\n> >> >> let me quote from that email here:\n> >> >>\n> >> >>> If you want to have an easy example of a custom merge strategy, then let's\n> >> >>> have that easy example. `git-merge-resolve.sh` ain't that example.\n> >> >>>\n> >> >>> It would be a different matter if you had commented about\n> >> >>> `git-merge-ours.sh`:\n> >> >>> https://github.com/git/git/blob/v2.17.0/contrib/examples/git-merge-ours.sh\n> >> >>> That _was_ a simple and easy example.\n> >> >>\n> >> >> ...and it was _utterly useless_ as an example.  It only checked that\n> >> >> the user hadn't modified the index since HEAD.  It doesn't demonstrate\n> >> >> anything about how to merge differing entries, since that merge\n> >> >> strategy specifically ignores changes made on the other side.  Since\n> >> >> merging differing entries is the whole point of writing a strategy, I\n> >> >> see no educational value in that particular script.\n> >> >>\n> >> >> `git-merge-resolve.sh` may be an imperfect example, but it's certainly\n> >> >> far superior to that.\n> >> >> ...\n> >> >> If someone makes a better example (which I agree could be done,\n> >> >> especially if it added lots of comments about what was required and\n> >> >> why), and ensures we keep useful test coverage (maybe using Junio's\n> >> >> c-resolve suggestion in another email), then my concerns about\n> >> >> reimplementing git-merge-resolve.sh in C go away.\n> >> >>\n> >> >> If that happens, then I still think it's a useless exercise to do the\n> >> >> reimplementation -- unless someone can provide evidence of `-s\n> >> >> resolve` being in use -- but it's not a harmful exercise and wouldn't\n> >> >> concern me.\n> >> >>\n> >> >> If the better example and mechanism to retain good test coverage\n> >> >> aren't provided, then I worry that reimplementing is a bunch of work\n> >> >> for an at best theoretical benefit, coupled with a double whammy\n> >> >> practical regression.\n> >> >\n> >> > Ah, you said many things I wanted to say already.  Thanks.\n> >>\n> >> I may have missed something in this thread, but wouldn't an acceptable\n> >> way to please everyone here be to:\n> >>\n> >>  1. Have git's behavior be that of the end of this series...\n> >>  2. Add a GIT_TEST_* mode where we'll optionally invoke these \"built-in\"\n> >>     merge strategies as commands, i.e. have them fall back to\n> >>     \"try_merge_command()\".\n> >\n> > In the portion of the email you quoted and responded to, most of the\n> > text was talking about how git-merge-resolve.sh serves an important\n> > educational purpose, yet you've only tried to address the testing\n> > issue.  I think both are important.\n>\n> *Nod*, I meant (but should have said) \"on the topic of the test\n>  coverage\"...\n\nAh, yes, that would have helped.  :-)\n\n> > The easiest way to fix the\n> > educational shortcoming of this series is to reverse the deleting of\n> > git-merge-resolve.sh, and restore the building and distribution of\n> > git-merge-resolve from that script.  Unfortunately, that generates a\n> > collision between both the script and the builtin being used to build\n> > the same file (namely, git-merge-resolve)...\n>\n> I'd think if we were shipping it as an example we could give it a\n> different name, or not install it as an executable, but in the \"shared\"\n> part (along with the README etc.).\n\nSeems reasonable; I'm slightly partial to the name\n\"git-merge-strategy-demo\" (though \"--strategy strategy-demo\" might\nlook weird), or perhaps just \"git-merge-demo\" (though that makes\npeople wonder what kind of demo).\n\n> Or keep it in-tree in contrib, but we did try that sort of thing before\n> with 49eb8d39c78 (Remove contrib/examples/*, 2018-03-25) :)\n>\n> I think the best way forward is to just note in the documentation some\n> examples of how to write a merge driver, either by linking to an older\n> version of the script, or quoting it inline.\n\nNitpick: \"merge strategy\", not \"merge driver\".\n\nA merge driver is something defined in .gitattributes and only ever\nfunctions on three versions of one file, never having bigger knowledge\nof the wider tree.  A merge driver is thus a special purpose three-way\ncontent merge of a single file (replacing the normal xdiff merge\nstuff) tailored to a specific file type.\n\nA merge strategy, in contrast, is given multiple commits to merge and\nthus has a view of the whole tree.  A merge strategy needs to decide\nwhether and how to handle directory/file conflicts, differing modes,\nsubmodule updates, recursive ancestor consolidation, file renames\n(including weird cases like colliding renames or renamed differently),\ndirectory renames, etc.  A merge strategy may well call various merge\ndrivers (assuming some are defined in .gitattributes) for different\npaths within the tree, and/or fall back to calling (directly or\nindirectly) the code in xdiff to handle the three-way merge of\nindividual files.\n\n> > which is yet another\n> > reason that the easiest solution available here is to just not rewrite\n> > this script in C at all.\n>\n> I think there's bigger benefits to moving more things to C & built-ins,\n> so I'd prefer to see some version of this where what we do by default is\n> to call this C code (or similar), and not as a sub-process.\n\nYes, in general I agree there are big benefits to moving towards C &\nbuilt-ins.  I'm unconvinced any of them apply in the specific case of\nmerge-resolve, as noted at length earlier in this thread.\n\nIf someone wants to do it anyway, they should just make sure that (1)\ntesting of external merge strategies doesn't regress and remains well\ntested, and (2) there is a good story for educating users about how to\nwrite external merge strategies, or at least as good as what we have\nnow.  If I feel either is being ignored or regressing, I'll likely\nexpress my concerns again.\n\n> > There are certainly other possible solutions to the educational issue,\n> > and might not even be too hard, but we'd need someone to implement one\n> > before I'd agree we found an \"acceptable way to please everyone\".  :-)\n>\n> *nod*\n>\n> >> So something like this on top of this series (assume my SOB etc. if this\n> >> is acceptable). I only tested this locally, but it seems to do the right\n> >> thing for me:\n> > <snip patch>\n> >\n> > How did you test?  I'm a bit confused...unless I'm misreading\n> > something, it appears to me that ci/lib.sh sets SKIP_DASHED_BUILT_INS\n> > unconditionally which would probably cause your proposal to break.\n>\n> Admittedly not very thoroughly, but I'm fairly sure it does the right\n> thing when it comes to this, and SKIP_DASHED_BUILT_INS doesn't enter\n> into it (and all my local builds use SKIP_DASHED_BUILT_INS=Y).\n>\n> The try_merge_command() invokes merge-what-ever, and does a\n> run_command_v_opt(args.v, RUN_GIT_CMD). At that point we'll invoke a\n> \"git merge-what-ever\", i.e. we don't need a \"git-merge-what-ever\" binary\n> to exist.\n>\n> This is what we do in general when git is invoking itself, and we'd need\n> to go out of our way to have it not work in this case (i.e. build it as\n> a stand-alone program, like git-http-fetch, and not as a built-in).\n\nOh, right, I was mixing up git-merge-one-file (which merge-resolve has\nmerge-index invoke, and yes including the dash right after \"git\") and\n`git merge-resolve`.  Sorry about that.\n"},{"id":"467482","messageId":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"20220809185429.20098-1-alban.gruin@gmail.com","subject":"[PATCH v9 00/12] merge-index: prepare to rewrite merge drivers in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:17Z","receivedAt":"2022-11-18T11:18:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This is a prep series for a re-roll of Alban Gruin's series to rewrite\nvarious merge drivers from *.sh to *.c, and being able to call those\nin-process.\n\nThis was last discussed on-list in August[1], and has been ejected\nfrom \"seen\" due to staleness.\n\nThe last time around there were concerns with the later part of this\ntopic, but the parts that are included here weren't controversial,\nthose will be part 2 (and I think I've addressed those concerns).\n\nChanges since the v8:\n\n * Migrate \"merge-index\" to parse_options(), first in a bug-for-bug\n   compatible way, and then later on fix the behavior, and add tests\n   along the way.\n\n * The 5-9/12 here were all split out in one way or another from\n   Alban's 3/14[2], in such a way as to make the diff for 10/12 as\n   friendly as possible (e.g. catering to rename detection).\n\n * Alban's converted die()/exit() in the built-in to \"error()\", but in\n   doing so introduced a behavior change: When we'd previously process\n   N items we'd exit right away, but in the v8 we'd attempt all N\n   items.\n\n   It turns out that almost nothing that came after care about the\n   die(), i.e. even if we've lib-ified it it's OK to call die() within\n   that library.\n\n * A new 11/12 hopefully makes the way merge-index parses out OIDs and\n   types easier to reason about.\n\n * Finally, 12/12 makes the semantics of \"merge-index\" sane vis-a-vis\n   parse_options().\n\nPassing CI and branch for this at\nhttps://github.com/avar/git/tree/ag/merge-strategies-in-c-prep\n\nThe follow-on from this is then\nhttps://github.com/avar/git/tree/ag/merge-strategies-in-c-2, for those\nthat want to peek ahead.\n\n1. https://lore.kernel.org/git/20220809185429.20098-9-alban.gruin@gmail.com/\n2. https://lore.kernel.org/git/20220809185429.20098-4-alban.gruin@gmail.com/\n\nAlban Gruin (4):\n  t6060: modify multiple files to expose a possible issue with\n    merge-index\n  t6060: add tests for removed files\n  merge-index: improve die() error messages\n  merge-index: libify merge_one_path() and merge_all()\n\nÆvar Arnfjörð Bjarmason (8):\n  merge-index doc & -h: fix padding, labels and \"()\" use\n  merge-index tests: add usage tests\n  merge-index: migrate to parse_options() API\n  merge-index i18n: mark die() messages for translation\n  merge-index: stop calling ensure_full_index() twice\n  builtin/merge-index.c: don't USE_THE_INDEX_COMPATIBILITY_MACROS\n  merge-index: use \"struct strvec\" and helper to prepare args\n  merge-index: make the argument parsing sensible & simpler\n\n Documentation/git-merge-index.txt |   2 +-\n Makefile                          |   1 +\n builtin/merge-index.c             | 169 ++++++++++++++----------------\n git.c                             |   2 +-\n merge-strategies.c                |  87 +++++++++++++++\n merge-strategies.h                |  19 ++++\n t/t0450/txt-help-mismatches       |   1 -\n t/t6060-merge-index.sh            |  65 +++++++++++-\n 8 files changed, 250 insertions(+), 96 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v8:\n -:  ----------- >  1:  cafc7db374e merge-index doc & -h: fix padding, labels and \"()\" use\n 1:  0f791f500e6 !  2:  099d4812601 t6060: modify multiple files to expose a possible issue with merge-index\n    @@ Commit message\n         be trivially mergeable.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## t/t6060-merge-index.sh ##\n    -@@ t/t6060-merge-index.sh: test_description='basic git merge-index / git-merge-one-file tests'\n    +@@ t/t6060-merge-index.sh: TEST_PASSES_SANITIZE_LEAK=true\n      \n      test_expect_success 'setup diverging branches' '\n      \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n 2:  ed9e7a45855 !  3:  af3a235a224 t6060: add tests for removed files\n    @@ Commit message\n         tagged as `base', and deletes it in the commit tagged as `two'.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## t/t6060-merge-index.sh ##\n    -@@ t/t6060-merge-index.sh: test_description='basic git merge-index / git-merge-one-file tests'\n    +@@ t/t6060-merge-index.sh: TEST_PASSES_SANITIZE_LEAK=true\n      test_expect_success 'setup diverging branches' '\n      \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n      \tcp file file2 &&\n -:  ----------- >  4:  7d686637fa3 merge-index tests: add usage tests\n -:  ----------- >  5:  845f9b0cc19 merge-index: migrate to parse_options() API\n -:  ----------- >  6:  fc4e64f669e merge-index: improve die() error messages\n -:  ----------- >  7:  04c2bae9e68 merge-index i18n: mark die() messages for translation\n -:  ----------- >  8:  badfc60354a merge-index: stop calling ensure_full_index() twice\n -:  ----------- >  9:  f29343197eb builtin/merge-index.c: don't USE_THE_INDEX_COMPATIBILITY_MACROS\n 3:  d1d5740a8e5 ! 10:  c7a131a9a86 merge-index: libify merge_one_path() and merge_all()\n    @@ Metadata\n      ## Commit message ##\n         merge-index: libify merge_one_path() and merge_all()\n     \n    -    The \"resolve\" and \"octopus\" merge strategies do not call directly `git\n    -    merge-one-file', they delegate the work to another git command, `git\n    -    merge-index', that will loop over files in the index and call the\n    -    specified command.  Unfortunately, these functions are not part of\n    -    libgit.a, which means that once rewritten, the strategies would still\n    -    have to invoke `merge-one-file' by spawning a new process first.\n    +    Move the workhorse functions in \"builtin/merge-index.c\" into a new\n    +    \"merge-strategies\" library, and mostly \"libify\" the code while doing\n    +    so.\n     \n    -    To avoid this, this moves and renames merge_one_path(), merge_all(), and\n    -    their helpers to merge-strategies.c.  They also take a callback to\n    -    dictate what they should do for each file.  For now, to preserve the\n    -    behaviour of `merge-index', only one callback, launching a new process,\n    -    is defined.\n    +    Eventually this will allow us to invoke merge strategies such as\n    +    \"resolve\" and \"octopus\" in-process, once we've followed-up and\n    +    replaced \"git-merge-{resolve,octopus}.sh\" etc.\n    +\n    +    But for now let's move this code, while trying to optimize for as much\n    +    of it as possible being highlighted by the diff rename detection.\n    +\n    +    We still call die() in this library. An earlier version of this[1]\n    +    converted these to \"error()\", but the problem with that that we'd then\n    +    potentially run into the same error N times, e.g. once for every\n    +    \"<file>\" we were asked to operate on, instead of dying on the first\n    +    case. So let's leave those to \"die()\" for now.\n    +\n    +    1. https://lore.kernel.org/git/20220809185429.20098-4-alban.gruin@gmail.com/\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## Makefile ##\n     @@ Makefile: LIB_OBJS += merge-blobs.o\n    @@ Makefile: LIB_OBJS += merge-blobs.o\n     \n      ## builtin/merge-index.c ##\n     @@\n    - #define USE_THE_INDEX_COMPATIBILITY_MACROS\n      #include \"builtin.h\"\n    + #include \"parse-options.h\"\n     +#include \"merge-strategies.h\"\n      #include \"run-command.h\"\n      \n    - static const char *pgm;\n    +-static const char *pgm;\n     -static int one_shot, quiet;\n     -static int err;\n    ++struct mofs_data {\n    ++\tconst char *program;\n    ++};\n      \n    --static int merge_entry(int pos, const char *path)\n    -+static int merge_one_file_spawn(struct index_state *istate,\n    -+\t\t\t\tconst struct object_id *orig_blob,\n    -+\t\t\t\tconst struct object_id *our_blob,\n    -+\t\t\t\tconst struct object_id *their_blob, const char *path,\n    -+\t\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    -+\t\t\t\tvoid *data)\n    +-static int merge_entry(struct index_state *istate, int pos, const char *path)\n    ++static int merge_one_file(struct index_state *istate,\n    ++\t\t\t  const struct object_id *orig_blob,\n    ++\t\t\t  const struct object_id *our_blob,\n    ++\t\t\t  const struct object_id *their_blob, const char *path,\n    ++\t\t\t  unsigned int orig_mode, unsigned int our_mode,\n    ++\t\t\t  unsigned int their_mode, void *data)\n      {\n     -\tint found;\n    --\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n    --\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n    --\tchar ownbuf[4][60];\n    -+\tchar oids[3][GIT_MAX_HEXSZ + 1] = {{0}};\n    -+\tchar modes[3][10] = {{0}};\n    -+\tconst char *arguments[] = { pgm, oids[0], oids[1], oids[2],\n    -+\t\t\t\t    path, modes[0], modes[1], modes[2], NULL };\n    ++\tstruct mofs_data *d = data;\n    ++\tconst char *pgm = d->program;\n    + \tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n    + \tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n    + \tchar ownbuf[4][60];\n    ++\tint stage = 0;\n    + \tstruct child_process cmd = CHILD_PROCESS_INIT;\n      \n    --\tif (pos >= active_nr)\n    --\t\tdie(\"git merge-index: %s not in the cache\", path);\n    +-\tif (pos >= istate->cache_nr)\n    +-\t\tdie(_(\"'%s' is not in the cache\"), path);\n     -\tfound = 0;\n     -\tdo {\n    --\t\tconst struct cache_entry *ce = active_cache[pos];\n    +-\t\tconst struct cache_entry *ce = istate->cache[pos];\n     -\t\tint stage = ce_stage(ce);\n     -\n     -\t\tif (strcmp(ce->name, path))\n    @@ builtin/merge-index.c\n     -\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n     -\t\targuments[stage] = hexbuf[stage];\n     -\t\targuments[stage + 4] = ownbuf[stage];\n    --\t} while (++pos < active_nr);\n    +-\t} while (++pos < istate->cache_nr);\n     -\tif (!found)\n    --\t\tdie(\"git merge-index: %s not in the cache\", path);\n    +-\t\tdie(_(\"'%s' is not in the cache\"), path);\n     -\n    --\tif (run_command_v_opt(arguments, 0)) {\n    +-\tstrvec_pushv(&cmd.args, arguments);\n    +-\tif (run_command(&cmd)) {\n     -\t\tif (one_shot)\n     -\t\t\terr++;\n     -\t\telse {\n     -\t\t\tif (!quiet)\n    --\t\t\t\tdie(\"merge program failed\");\n    +-\t\t\t\tdie(_(\"merge program failed\"));\n     -\t\t\texit(1);\n     -\t\t}\n    -+\tif (orig_blob) {\n    -+\t\toid_to_hex_r(oids[0], orig_blob);\n    -+\t\txsnprintf(modes[0], sizeof(modes[0]), \"%06o\", orig_mode);\n    ++#define ADD_MOF_ARG(oid, mode) \\\n    ++\tif ((oid)) { \\\n    ++\t\tstage++; \\\n    ++\t\toid_to_hex_r(hexbuf[stage], (oid)); \\\n    ++\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%06o\", (mode)); \\\n    ++\t\targuments[stage] = hexbuf[stage]; \\\n    ++\t\targuments[stage + 4] = ownbuf[stage]; \\\n      \t}\n     -\treturn found;\n     -}\n    - \n    --static void merge_one_path(const char *path)\n    --{\n    --\tint pos = cache_name_pos(path, strlen(path));\n     -\n    +-static void merge_one_path(struct index_state *istate, const char *path)\n    +-{\n    +-\tint pos = index_name_pos(istate, path, strlen(path));\n    + \n     -\t/*\n     -\t * If it already exists in the cache as stage0, it's\n     -\t * already merged and there is nothing to do.\n     -\t */\n     -\tif (pos < 0)\n    --\t\tmerge_entry(-pos-1, path);\n    +-\t\tmerge_entry(istate, -pos-1, path);\n     -}\n    -+\tif (our_blob) {\n    -+\t\toid_to_hex_r(oids[1], our_blob);\n    -+\t\txsnprintf(modes[1], sizeof(modes[1]), \"%06o\", our_mode);\n    -+\t}\n    - \n    --static void merge_all(void)\n    +-\n    +-static void merge_all(struct index_state *istate)\n     -{\n     -\tint i;\n    --\t/* TODO: audit for interaction with sparse-index. */\n    --\tensure_full_index(&the_index);\n    --\tfor (i = 0; i < active_nr; i++) {\n    --\t\tconst struct cache_entry *ce = active_cache[i];\n    ++\tADD_MOF_ARG(orig_blob, orig_mode);\n    ++\tADD_MOF_ARG(our_blob, our_mode);\n    ++\tADD_MOF_ARG(their_blob, their_mode);\n    + \n    +-\tfor (i = 0; i < istate->cache_nr; i++) {\n    +-\t\tconst struct cache_entry *ce = istate->cache[i];\n     -\t\tif (!ce_stage(ce))\n     -\t\t\tcontinue;\n    --\t\ti += merge_entry(i, ce->name)-1;\n    -+\tif (their_blob) {\n    -+\t\toid_to_hex_r(oids[2], their_blob);\n    -+\t\txsnprintf(modes[2], sizeof(modes[2]), \"%06o\", their_mode);\n    - \t}\n    -+\n    -+\treturn run_command_v_opt(arguments, 0);\n    +-\t\ti += merge_entry(istate, i, ce->name)-1;\n    +-\t}\n    ++\tstrvec_pushv(&cmd.args, arguments);\n    ++\treturn run_command(&cmd);\n      }\n      \n      int cmd_merge_index(int argc, const char **argv, const char *prefix)\n      {\n    --\tint i, force_file = 0;\n    -+\tint i, force_file = 0, err = 0, one_shot = 0, quiet = 0;\n    ++\tint err = 0;\n    + \tint all = 0;\n    ++\tint one_shot = 0;\n    ++\tint quiet = 0;\n    + \tconst char * const usage[] = {\n    + \t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n    + \t\tNULL\n    +@@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    + \t\tOPT_END(),\n    + \t};\n    + #undef OPT__MERGE_INDEX_ALL\n    ++\tstruct mofs_data data = { 0 };\n      \n      \t/* Without this we cannot rely on waitpid() to tell\n      \t * what happened to our children.\n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    - \t\tquiet = 1;\n    - \t\ti++;\n    - \t}\n    -+\n    - \tpgm = argv[i++];\n    -+\n    - \tfor (; i < argc; i++) {\n    - \t\tconst char *arg = argv[i];\n    - \t\tif (!force_file && *arg == '-') {\n    + \t/* <merge-program> and its options */\n    + \tif (!argc)\n    + \t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n    +-\tpgm = argv[0];\n    ++\tdata.program = argv[0];\n    + \targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n    + \tif (argc && all)\n    + \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    - \t\t\tif (!strcmp(arg, \"-a\")) {\n    --\t\t\t\tmerge_all();\n    -+\t\t\t\terr |= merge_all_index(&the_index, one_shot, quiet,\n    -+\t\t\t\t\t\t       merge_one_file_spawn, NULL);\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    - \t\t\tdie(\"git merge-index: unknown option %s\", arg);\n    - \t\t}\n    --\t\tmerge_one_path(arg);\n    -+\t\terr |= merge_index_path(&the_index, one_shot, quiet, arg,\n    -+\t\t\t\t\tmerge_one_file_spawn, NULL);\n    - \t}\n    + \tensure_full_index(the_repository->index);\n    + \n    + \tif (all)\n    +-\t\tmerge_all(the_repository->index);\n    ++\t\terr |= merge_all_index(the_repository->index, one_shot, quiet,\n    ++\t\t\t\t       merge_one_file, &data);\n    + \telse\n    + \t\tfor (size_t i = 0; i < argc; i++)\n    +-\t\t\tmerge_one_path(the_repository->index, argv[i]);\n    ++\t\t\terr |= merge_index_path(the_repository->index,\n    ++\t\t\t\t\t\tone_shot, quiet, argv[i],\n    ++\t\t\t\t\t\tmerge_one_file, &data);\n    + \n     -\tif (err && !quiet)\n    --\t\tdie(\"merge program failed\");\n    -+\n    +-\t\tdie(_(\"merge program failed\"));\n      \treturn err;\n      }\n     \n    @@ merge-strategies.c (new)\n     +#include \"merge-strategies.h\"\n     +\n     +static int merge_entry(struct index_state *istate, unsigned int pos,\n    -+\t\t       const char *path, int *err, merge_fn fn, void *data)\n    ++\t\t       const char *path, int *err, merge_index_fn fn,\n    ++\t\t       void *data)\n     +{\n     +\tint found = 0;\n    -+\tconst struct object_id *oids[3] = {NULL};\n    -+\tunsigned int modes[3] = {0};\n    ++\tconst struct object_id *oids[3] = { 0 };\n    ++\tunsigned int modes[3] = { 0 };\n     +\n    ++\t*err = 0;\n    ++\n    ++\tif (pos >= istate->cache_nr)\n    ++\t\tdie(_(\"'%s' is not in the cache\"), path);\n     +\tdo {\n     +\t\tconst struct cache_entry *ce = istate->cache[pos];\n     +\t\tint stage = ce_stage(ce);\n    @@ merge-strategies.c (new)\n     +\t\tmodes[stage - 1] = ce->ce_mode;\n     +\t} while (++pos < istate->cache_nr);\n     +\tif (!found)\n    -+\t\treturn error(_(\"%s is not in the cache\"), path);\n    ++\t\tdie(_(\"'%s' is not in the cache\"), path);\n     +\n    -+\tif (fn(istate, oids[0], oids[1], oids[2], path,\n    -+\t       modes[0], modes[1], modes[2], data))\n    ++\tif (fn(istate, oids[0], oids[1], oids[2], path, modes[0], modes[1],\n    ++\t       modes[2], data))\n     +\t\t(*err)++;\n     +\n     +\treturn found;\n     +}\n     +\n     +int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    -+\t\t     const char *path, merge_fn fn, void *data)\n    ++\t\t     const char *path, merge_index_fn fn, void *data)\n     +{\n    -+\tint pos = index_name_pos(istate, path, strlen(path)), ret, err = 0;\n    ++\tint err, ret;\n    ++\tint pos = index_name_pos(istate, path, strlen(path));\n     +\n     +\t/*\n     +\t * If it already exists in the cache as stage0, it's\n     +\t * already merged and there is nothing to do.\n     +\t */\n    -+\tif (pos < 0) {\n    -+\t\tret = merge_entry(istate, -pos - 1, path, &err, fn, data);\n    -+\t\tif (ret == -1)\n    -+\t\t\treturn -1;\n    -+\t\telse if (err) {\n    -+\t\t\tif (!quiet && !oneshot)\n    -+\t\t\t\terror(_(\"merge program failed\"));\n    -+\t\t\treturn 1;\n    -+\t\t}\n    ++\tif (pos >= 0)\n    ++\t\treturn 0;\n    ++\n    ++\tret = merge_entry(istate, -pos - 1, path, &err, fn, data);\n    ++\tif (ret < 0)\n    ++\t\treturn ret;\n    ++\tif (err) {\n    ++\t\tif (!quiet && !oneshot)\n    ++\t\t\tdie(_(\"merge program failed\"));\n    ++\t\treturn 1;\n     +\t}\n     +\treturn 0;\n     +}\n     +\n     +int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    -+\t\t    merge_fn fn, void *data)\n    ++\t\t    merge_index_fn fn, void *data)\n     +{\n    -+\tint err = 0, ret;\n    ++\tint err, ret;\n     +\tunsigned int i;\n     +\n    -+\t/* TODO: audit for interaction with sparse-index. */\n    -+\tensure_full_index(istate);\n     +\tfor (i = 0; i < istate->cache_nr; i++) {\n     +\t\tconst struct cache_entry *ce = istate->cache[i];\n     +\t\tif (!ce_stage(ce))\n     +\t\t\tcontinue;\n     +\n     +\t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n    -+\t\tif (ret > 0)\n    ++\t\tif (ret < 0)\n    ++\t\t\treturn ret;\n    ++\t\telse if (ret > 0)\n     +\t\t\ti += ret - 1;\n    -+\t\telse if (ret == -1)\n    -+\t\t\treturn -1;\n     +\n     +\t\tif (err && !oneshot) {\n     +\t\t\tif (!quiet)\n    -+\t\t\t\terror(_(\"merge program failed\"));\n    ++\t\t\t\tdie(_(\"merge program failed\"));\n     +\t\t\treturn 1;\n     +\t\t}\n     +\t}\n     +\n     +\tif (err && !quiet)\n    -+\t\terror(_(\"merge program failed\"));\n    ++\t\tdie(_(\"merge program failed\"));\n     +\treturn err;\n     +}\n     \n    @@ merge-strategies.h (new)\n     +#ifndef MERGE_STRATEGIES_H\n     +#define MERGE_STRATEGIES_H\n     +\n    -+#include \"object.h\"\n    -+\n    -+typedef int (*merge_fn)(struct index_state *istate,\n    -+\t\t\tconst struct object_id *orig_blob,\n    -+\t\t\tconst struct object_id *our_blob,\n    -+\t\t\tconst struct object_id *their_blob, const char *path,\n    -+\t\t\tunsigned int orig_mode, unsigned int our_mode, unsigned int their_mode,\n    -+\t\t\tvoid *data);\n    ++struct object_id;\n    ++struct index_state;\n    ++typedef int (*merge_index_fn)(struct index_state *istate,\n    ++\t\t\t      const struct object_id *orig_blob,\n    ++\t\t\t      const struct object_id *our_blob,\n    ++\t\t\t      const struct object_id *their_blob,\n    ++\t\t\t      const char *path, unsigned int orig_mode,\n    ++\t\t\t      unsigned int our_mode, unsigned int their_mode,\n    ++\t\t\t      void *data);\n     +\n     +int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n    -+\t\t     const char *path, merge_fn fn, void *data);\n    ++\t\t     const char *path, merge_index_fn fn, void *data);\n     +int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n    -+\t\t    merge_fn fn, void *data);\n    ++\t\t    merge_index_fn fn, void *data);\n     +\n     +#endif /* MERGE_STRATEGIES_H */\n    -\n    - ## t/t7607-merge-state.sh ##\n    -@@ t/t7607-merge-state.sh: test_expect_success 'Ensure we restore original state if no merge strategy handl\n    - \t# just hit conflicts, it completely fails and says that it cannot\n    - \t# handle this type of merge.\n    - \ttest_expect_code 2 git merge branch2 branch3 >output 2>&1 &&\n    --\tgrep \"fatal: merge program failed\" output &&\n    -+\tgrep \"error: merge program failed\" output &&\n    - \tgrep \"Should not be doing an octopus\" output &&\n    - \n    - \t# Make sure we did not leave stray changes around when no appropriate\n 4:  4b0420836c1 <  -:  ----------- merge-index: drop the index\n 5:  19a4fc52c57 <  -:  ----------- merge-index: add a new way to invoke `git-merge-one-file'\n 6:  376130c1334 <  -:  ----------- update-index: move add_cacheinfo() to read-cache.c\n 7:  e440127edf2 <  -:  ----------- merge-one-file: rewrite in C\n 8:  661c358836e <  -:  ----------- merge-resolve: rewrite in C\n 9:  388128cd351 <  -:  ----------- merge-recursive: move better_branch_name() to merge.c\n10:  1515e154bf5 <  -:  ----------- merge-octopus: rewrite in C\n11:  701c47371a7 <  -:  ----------- merge: use the \"resolve\" strategy without forking\n12:  17597d0cc57 <  -:  ----------- merge: use the \"octopus\" strategy without forking\n13:  cecfa666ecb <  -:  ----------- sequencer: use the \"resolve\" strategy without forking\n14:  a23c0491a1f <  -:  ----------- sequencer: use the \"octopus\" strategy without forking\n -:  ----------- > 11:  adb712ca7a5 merge-index: use \"struct strvec\" and helper to prepare args\n -:  ----------- > 12:  f0368560140 merge-index: make the argument parsing sensible & simpler\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467483","messageId":"patch-v9-01.12-cafc7db374e-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 01/12] merge-index doc & -h: fix padding, labels and \"()\" use","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:18Z","receivedAt":"2022-11-18T11:18:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Make the \"merge-index\" doc SYNOPSIS and \"-h\" output consistent with\none another, and small issues with it:\n\n- Whitespace padding, per e2f4e7e8c0f (doc txt & -h consistency:\n  correct padding around \"[]()\", 2022-10-13).\n\n- Use \"<file>\" consistently, rather than using \"<filename>\" in the\n  \"-h\" output, and \"<file>\" in the SYNOPSIS.\n\n- The \"-h\" version incorrectly claimed that the filename was optional,\n  but it's not.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-merge-index.txt | 2 +-\n builtin/merge-index.c             | 2 +-\n t/t0450/txt-help-mismatches       | 1 -\n 3 files changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt\nindex eea56b3154e..a297105d6d8 100644\n--- a/Documentation/git-merge-index.txt\n+++ b/Documentation/git-merge-index.txt\n@@ -9,7 +9,7 @@ git-merge-index - Run a merge for files needing merging\n SYNOPSIS\n --------\n [verse]\n-'git merge-index' [-o] [-q] <merge-program> (-a | ( [--] <file>...) )\n+'git merge-index' [-o] [-q] <merge-program> (-a | ([--] <file>...))\n \n DESCRIPTION\n -----------\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 012f52bd007..1a5a64afd2a 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -80,7 +80,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tsignal(SIGCHLD, SIG_DFL);\n \n \tif (argc < 3)\n-\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n+\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\");\n \n \tread_cache();\n \ndiff --git a/t/t0450/txt-help-mismatches b/t/t0450/txt-help-mismatches\nindex a0777acd667..9e73c1892ae 100644\n--- a/t/t0450/txt-help-mismatches\n+++ b/t/t0450/txt-help-mismatches\n@@ -34,7 +34,6 @@ mailsplit\n maintenance\n merge\n merge-file\n-merge-index\n merge-one-file\n multi-pack-index\n name-rev\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467484","messageId":"patch-v9-03.12-af3a235a224-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 03/12] t6060: add tests for removed files","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:20Z","receivedAt":"2022-11-18T11:18:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nUntil now, t6060 did not not check git-merge-one-file's behaviour when a\nfile is deleted in a branch.  To avoid regressions on this during the\nconversion from shell to C, this adds a new file, `file3', in the commit\ntagged as `base', and deletes it in the commit tagged as `two'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t6060-merge-index.sh | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 30513351c23..079151ee06d 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -8,12 +8,14 @@ TEST_PASSES_SANITIZE_LEAK=true\n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n \tcp file file2 &&\n-\tgit add file file2 &&\n+\tcp file file3 &&\n+\tgit add file file2 file3 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n \tcp file file2 &&\n+\tgit rm file3 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n@@ -41,6 +43,7 @@ test_expect_success 'read-tree does not resolve content merge' '\n \tcat >expect <<-\\EOF &&\n \tfile\n \tfile2\n+\tfile3\n \tEOF\n \tgit read-tree -i -m base ten two &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467485","messageId":"patch-v9-02.12-099d4812601-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 02/12] t6060: modify multiple files to expose a possible issue with merge-index","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:19Z","receivedAt":"2022-11-18T11:18:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nCurrently, merge-index iterates over every index entry, skipping stage0\nentries.  It will then count how many entries following the current one\nhave the same name, then fork to do the merge.  It will then increase\nthe iterator by the number of entries to skip them.  This behaviour is\ncorrect, as even if the subprocess modifies the index, merge-index does\nnot reload it at all.\n\nBut when it will be rewritten to use a function, the index it will use\nwill be modified and may shrink when a conflict happens or if a file is\nremoved, so we have to be careful to handle such cases.\n\nHere is an example:\n\n *    Merge branches, file1 and file2 are trivially mergeable.\n |\\\n | *  Modifies file1 and file2.\n * |  Modifies file1 and file2.\n |/\n *    Adds file1 and file2.\n\nWhen the merge happens, the index will look like that:\n\n i -> 0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n      3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\nmerge-index handles `file1' first.  As it appears 3 times after the\niterator, it is merged.  The index is now stale, `i' is increased by 3,\nand the index now looks like this:\n\n      0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n i -> 3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\n`file2' appears three times too, so it is merged.\n\nWith a naive rewrite, the index would look like this:\n\n      0. file1 (stage0)\n      1. file2 (stage1)\n      2. file2 (stage2)\n i -> 3. file2 (stage3)\n\n`file2' appears once at the iterator or after, so it will be added,\n_not_ merged.  Which is wrong.\n\nA naive rewrite would lead to unproperly merged files, or even files not\nhandled at all.\n\nThis changes t6060 to reproduce this case, by creating 2 files instead\nof 1, to check the correctness of the soon-to-be-rewritten merge-index.\nThe files are identical, which is not really important -- the factors\nthat could trigger this issue are that they should be separated by at\nmost one entry in the index, and that the first one in the index should\nbe trivially mergeable.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t6060-merge-index.sh | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 1a8b64cce18..30513351c23 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -7,16 +7,19 @@ TEST_PASSES_SANITIZE_LEAK=true\n \n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n-\tgit add file &&\n+\tcp file file2 &&\n+\tgit add file file2 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n \tsed s/10/ten/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m ten &&\n \tgit tag ten\n '\n@@ -35,8 +38,11 @@ ten\n EOF\n \n test_expect_success 'read-tree does not resolve content merge' '\n+\tcat >expect <<-\\EOF &&\n+\tfile\n+\tfile2\n+\tEOF\n \tgit read-tree -i -m base ten two &&\n-\techo file >expect &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_cmp expect unmerged\n '\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467486","messageId":"patch-v9-05.12-845f9b0cc19-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 05/12] merge-index: migrate to parse_options() API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:22Z","receivedAt":"2022-11-18T11:18:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Migrate the \"merge-index\" command to the parse_options() API, a\npreceding commit added tests for the existing behavior.\n\nIn a subsequent commit we'll adjust the behavior to be more consistent\nwith how most other commands work, but for now let's take pains to\npreserve it as-is. We need to e.g. call parse_options() twice now, as\nthe \"-a\" option is currently only understood after \"<merge-program>\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c  | 71 ++++++++++++++++++++++++++----------------\n git.c                  |  2 +-\n t/t6060-merge-index.sh | 10 +++---\n 3 files changed, 51 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 1a5a64afd2a..3bd0790465e 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,5 +1,6 @@\n #define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n+#include \"parse-options.h\"\n #include \"run-command.h\"\n \n static const char *pgm;\n@@ -72,7 +73,26 @@ static void merge_all(void)\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint all = 0;\n+\tconst char * const usage[] = {\n+\t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n+\t\tNULL\n+\t};\n+#define OPT__MERGE_INDEX_ALL(v) \\\n+\tOPT_BOOL('a', NULL, (v), \\\n+\t\t N_(\"merge all files in the index that need merging\"))\n+\tstruct option options[] = {\n+\t\tOPT_BOOL('o', NULL, &one_shot,\n+\t\t\t N_(\"don't stop at the first failed merge\")),\n+\t\tOPT__QUIET(&quiet, N_(\"be quiet\")),\n+\t\tOPT__MERGE_INDEX_ALL(&all), /* include \"-a\" to show it in \"-bh\" */\n+\t\tOPT_END(),\n+\t};\n+\tstruct option options_prog[] = {\n+\t\tOPT__MERGE_INDEX_ALL(&all),\n+\t\tOPT_END(),\n+\t};\n+#undef OPT__MERGE_INDEX_ALL\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -80,38 +100,35 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tsignal(SIGCHLD, SIG_DFL);\n \n \tif (argc < 3)\n-\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\");\n+\t\tusage_with_options(usage, options);\n+\n+\t/* Option parsing without <merge-program> options */\n+\targc = parse_options(argc, argv, prefix, options, usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (all)\n+\t\tusage_msg_optf(_(\"'%s' option can only be provided after '<merge-program>'\"),\n+\t\t\t      usage, options, \"-a\");\n+\t/* <merge-program> and its options */\n+\tif (!argc)\n+\t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n+\tpgm = argv[0];\n+\targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n+\tif (argc && all)\n+\t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n+\t\t\t      usage, options);\n \n \tread_cache();\n \n \t/* TODO: audit for interaction with sparse-index. */\n \tensure_full_index(&the_index);\n \n-\ti = 1;\n-\tif (!strcmp(argv[i], \"-o\")) {\n-\t\tone_shot = 1;\n-\t\ti++;\n-\t}\n-\tif (!strcmp(argv[i], \"-q\")) {\n-\t\tquiet = 1;\n-\t\ti++;\n-\t}\n-\tpgm = argv[i++];\n-\tfor (; i < argc; i++) {\n-\t\tconst char *arg = argv[i];\n-\t\tif (!force_file && *arg == '-') {\n-\t\t\tif (!strcmp(arg, \"--\")) {\n-\t\t\t\tforce_file = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tdie(\"git merge-index: unknown option %s\", arg);\n-\t\t}\n-\t\tmerge_one_path(arg);\n-\t}\n+\n+\tif (all)\n+\t\tmerge_all();\n+\telse\n+\t\tfor (size_t i = 0; i < argc; i++)\n+\t\t\tmerge_one_path(argv[i]);\n+\n \tif (err && !quiet)\n \t\tdie(\"merge program failed\");\n \treturn err;\ndiff --git a/git.c b/git.c\nindex 6662548986f..83696fd8b4a 100644\n--- a/git.c\n+++ b/git.c\n@@ -560,7 +560,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n-\t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-index\", cmd_merge_index, RUN_SETUP },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex edc03b41ab9..6c59e7bc4e5 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -22,9 +22,10 @@ test_expect_success 'usage: 2 arguments' '\n \n test_expect_success 'usage: -a before <program>' '\n \tcat >expect <<-\\EOF &&\n-\tfatal: git merge-index: b not in the cache\n+\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n \tEOF\n-\ttest_expect_code 128 git merge-index -a b program >out 2>actual &&\n+\ttest_expect_code 129 git merge-index -a b program >out 2>actual.raw &&\n+\tgrep \"^fatal:\" actual.raw >actual &&\n \ttest_must_be_empty out &&\n \ttest_cmp expect actual\n '\n@@ -33,9 +34,10 @@ for opt in -q -o\n do\n \ttest_expect_success \"usage: $opt after -a\" '\n \t\tcat >expect <<-EOF &&\n-\t\tfatal: git merge-index: unknown option $opt\n+\t\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n \t\tEOF\n-\t\ttest_expect_code 128 git merge-index -a $opt >out 2>actual &&\n+\t\ttest_expect_code 129 git merge-index -a $opt >out 2>actual.raw &&\n+\t\tgrep \"^fatal:\" actual.raw >actual &&\n \t\ttest_must_be_empty out &&\n \t\ttest_cmp expect actual\n \t'\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467487","messageId":"patch-v9-07.12-04c2bae9e68-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 07/12] merge-index i18n: mark die() messages for translation","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:24Z","receivedAt":"2022-11-18T11:18:58Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Mark the die() messages for translation with _(). We don't rely on the\nspecifics of these messages as plumbing, so they can be safely\ntranslated.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 0b06c69354b..ee48587a8fb 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path)\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \tif (pos >= active_nr)\n-\t\tdie(\"'%s' is not in the cache\", path);\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n \tfound = 0;\n \tdo {\n \t\tconst struct cache_entry *ce = active_cache[pos];\n@@ -31,7 +31,7 @@ static int merge_entry(int pos, const char *path)\n \t\targuments[stage + 4] = ownbuf[stage];\n \t} while (++pos < active_nr);\n \tif (!found)\n-\t\tdie(\"'%s' is not in the cache\", path);\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n \n \tstrvec_pushv(&cmd.args, arguments);\n \tif (run_command(&cmd)) {\n@@ -39,7 +39,7 @@ static int merge_entry(int pos, const char *path)\n \t\t\terr++;\n \t\telse {\n \t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n+\t\t\t\tdie(_(\"merge program failed\"));\n \t\t\texit(1);\n \t\t}\n \t}\n@@ -130,6 +130,6 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\tmerge_one_path(argv[i]);\n \n \tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n+\t\tdie(_(\"merge program failed\"));\n \treturn err;\n }\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467488","messageId":"patch-v9-04.12-7d686637fa3-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 04/12] merge-index tests: add usage tests","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:21Z","receivedAt":"2022-11-18T11:19:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add tests that stress the current behavior of the options parsing in\ncmd_merge_index(), in preparation for moving it over to\nparse_options().\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t6060-merge-index.sh | 44 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 44 insertions(+)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 079151ee06d..edc03b41ab9 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -5,6 +5,50 @@ test_description='basic git merge-index / git-merge-one-file tests'\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n+test_expect_success 'usage: 1 argument' '\n+\ttest_expect_code 129 git merge-index a >out 2>err &&\n+\ttest_must_be_empty out &&\n+\tgrep ^usage err\n+'\n+\n+test_expect_success 'usage: 2 arguments' '\n+\tcat >expect <<-\\EOF &&\n+\tfatal: git merge-index: b not in the cache\n+\tEOF\n+\ttest_expect_code 128 git merge-index a b >out 2>actual &&\n+\ttest_must_be_empty out &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'usage: -a before <program>' '\n+\tcat >expect <<-\\EOF &&\n+\tfatal: git merge-index: b not in the cache\n+\tEOF\n+\ttest_expect_code 128 git merge-index -a b program >out 2>actual &&\n+\ttest_must_be_empty out &&\n+\ttest_cmp expect actual\n+'\n+\n+for opt in -q -o\n+do\n+\ttest_expect_success \"usage: $opt after -a\" '\n+\t\tcat >expect <<-EOF &&\n+\t\tfatal: git merge-index: unknown option $opt\n+\t\tEOF\n+\t\ttest_expect_code 128 git merge-index -a $opt >out 2>actual &&\n+\t\ttest_must_be_empty out &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"usage: $opt program\" '\n+\t\ttest_expect_code 0 git merge-index $opt program\n+\t'\n+done\n+\n+test_expect_success 'usage: program' '\n+\ttest_expect_code 129 git merge-index program\n+'\n+\n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n \tcp file file2 &&\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467489","messageId":"patch-v9-06.12-fc4e64f669e-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 06/12] merge-index: improve die() error messages","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:23Z","receivedAt":"2022-11-18T11:19:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nOur usual convention is not to repeat the program name back at the\nuser, and to quote path arguments. Let's do that now to reduce the\nsize of the subsequent commit.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c  | 4 ++--\n t/t6060-merge-index.sh | 2 +-\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 3bd0790465e..0b06c69354b 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path)\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \tif (pos >= active_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n+\t\tdie(\"'%s' is not in the cache\", path);\n \tfound = 0;\n \tdo {\n \t\tconst struct cache_entry *ce = active_cache[pos];\n@@ -31,7 +31,7 @@ static int merge_entry(int pos, const char *path)\n \t\targuments[stage + 4] = ownbuf[stage];\n \t} while (++pos < active_nr);\n \tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n+\t\tdie(\"'%s' is not in the cache\", path);\n \n \tstrvec_pushv(&cmd.args, arguments);\n \tif (run_command(&cmd)) {\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 6c59e7bc4e5..bc201a69552 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -13,7 +13,7 @@ test_expect_success 'usage: 1 argument' '\n \n test_expect_success 'usage: 2 arguments' '\n \tcat >expect <<-\\EOF &&\n-\tfatal: git merge-index: b not in the cache\n+\tfatal: '\\''b'\\'' is not in the cache\n \tEOF\n \ttest_expect_code 128 git merge-index a b >out 2>actual &&\n \ttest_must_be_empty out &&\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467490","messageId":"patch-v9-08.12-badfc60354a-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 08/12] merge-index: stop calling ensure_full_index() twice","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:25Z","receivedAt":"2022-11-18T11:19:06Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"When most of the ensure_full_index() calls were added in\n8e97852919f (Merge branch 'ds/sparse-index-protections', 2021-04-30)\nwe could add them at the start of cmd_*() for built-ins, but in some\ncases we couldn't do that, as we'd only want to initialize the index\nconditionally on some branches in the code.\n\nBut this code added in 299e2c4561b (merge-index: ensure full index,\n2021-04-01) (part of 8e97852919f) isn't such a case. The merge_all()\nfunction is only called by cmd_merge_index(), which before calling it\nwill have called ensure_full_index() unconditionally.\n\nWe can therefore skip this. While we're at it, and mainly so that\nwe'll see the relevant code in the context, let's fix a minor\nwhitespace issue that the addition of the ensure_full_index() call in\n299e2c4561b introduced.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex ee48587a8fb..9bffcc5b0f1 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -61,8 +61,7 @@ static void merge_one_path(const char *path)\n static void merge_all(void)\n {\n \tint i;\n-\t/* TODO: audit for interaction with sparse-index. */\n-\tensure_full_index(&the_index);\n+\n \tfor (i = 0; i < active_nr; i++) {\n \t\tconst struct cache_entry *ce = active_cache[i];\n \t\tif (!ce_stage(ce))\n@@ -122,7 +121,6 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t/* TODO: audit for interaction with sparse-index. */\n \tensure_full_index(&the_index);\n \n-\n \tif (all)\n \t\tmerge_all();\n \telse\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467491","messageId":"patch-v9-10.12-c7a131a9a86-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 10/12] merge-index: libify merge_one_path() and merge_all()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:27Z","receivedAt":"2022-11-18T11:19:10Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nMove the workhorse functions in \"builtin/merge-index.c\" into a new\n\"merge-strategies\" library, and mostly \"libify\" the code while doing\nso.\n\nEventually this will allow us to invoke merge strategies such as\n\"resolve\" and \"octopus\" in-process, once we've followed-up and\nreplaced \"git-merge-{resolve,octopus}.sh\" etc.\n\nBut for now let's move this code, while trying to optimize for as much\nof it as possible being highlighted by the diff rename detection.\n\nWe still call die() in this library. An earlier version of this[1]\nconverted these to \"error()\", but the problem with that that we'd then\npotentially run into the same error N times, e.g. once for every\n\"<file>\" we were asked to operate on, instead of dying on the first\ncase. So let's leave those to \"die()\" for now.\n\n1. https://lore.kernel.org/git/20220809185429.20098-4-alban.gruin@gmail.com/\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Makefile              |  1 +\n builtin/merge-index.c | 95 ++++++++++++++++---------------------------\n merge-strategies.c    | 87 +++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    | 19 +++++++++\n 4 files changed, 142 insertions(+), 60 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex 4927379184c..ccd467cec79 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1000,6 +1000,7 @@ LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-ort-wrappers.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += midx.o\n LIB_OBJS += name-hash.o\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex c269d76cc8f..21598a52383 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,77 +1,50 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"merge-strategies.h\"\n #include \"run-command.h\"\n \n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n+struct mofs_data {\n+\tconst char *program;\n+};\n \n-static int merge_entry(struct index_state *istate, int pos, const char *path)\n+static int merge_one_file(struct index_state *istate,\n+\t\t\t  const struct object_id *orig_blob,\n+\t\t\t  const struct object_id *our_blob,\n+\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t  unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t  unsigned int their_mode, void *data)\n {\n-\tint found;\n+\tstruct mofs_data *d = data;\n+\tconst char *pgm = d->program;\n \tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n \tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n \tchar ownbuf[4][60];\n+\tint stage = 0;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\tif (pos >= istate->cache_nr)\n-\t\tdie(_(\"'%s' is not in the cache\"), path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = istate->cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < istate->cache_nr);\n-\tif (!found)\n-\t\tdie(_(\"'%s' is not in the cache\"), path);\n-\n-\tstrvec_pushv(&cmd.args, arguments);\n-\tif (run_command(&cmd)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(_(\"merge program failed\"));\n-\t\t\texit(1);\n-\t\t}\n+#define ADD_MOF_ARG(oid, mode) \\\n+\tif ((oid)) { \\\n+\t\tstage++; \\\n+\t\toid_to_hex_r(hexbuf[stage], (oid)); \\\n+\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%06o\", (mode)); \\\n+\t\targuments[stage] = hexbuf[stage]; \\\n+\t\targuments[stage + 4] = ownbuf[stage]; \\\n \t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(struct index_state *istate, const char *path)\n-{\n-\tint pos = index_name_pos(istate, path, strlen(path));\n \n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(istate, -pos-1, path);\n-}\n-\n-static void merge_all(struct index_state *istate)\n-{\n-\tint i;\n+\tADD_MOF_ARG(orig_blob, orig_mode);\n+\tADD_MOF_ARG(our_blob, our_mode);\n+\tADD_MOF_ARG(their_blob, their_mode);\n \n-\tfor (i = 0; i < istate->cache_nr; i++) {\n-\t\tconst struct cache_entry *ce = istate->cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(istate, i, ce->name)-1;\n-\t}\n+\tstrvec_pushv(&cmd.args, arguments);\n+\treturn run_command(&cmd);\n }\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n+\tint err = 0;\n \tint all = 0;\n+\tint one_shot = 0;\n+\tint quiet = 0;\n \tconst char * const usage[] = {\n \t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n \t\tNULL\n@@ -91,6 +64,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tOPT_END(),\n \t};\n #undef OPT__MERGE_INDEX_ALL\n+\tstruct mofs_data data = { 0 };\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -109,7 +83,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t/* <merge-program> and its options */\n \tif (!argc)\n \t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n-\tpgm = argv[0];\n+\tdata.program = argv[0];\n \targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n \tif (argc && all)\n \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n@@ -121,12 +95,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tensure_full_index(the_repository->index);\n \n \tif (all)\n-\t\tmerge_all(the_repository->index);\n+\t\terr |= merge_all_index(the_repository->index, one_shot, quiet,\n+\t\t\t\t       merge_one_file, &data);\n \telse\n \t\tfor (size_t i = 0; i < argc; i++)\n-\t\t\tmerge_one_path(the_repository->index, argv[i]);\n+\t\t\terr |= merge_index_path(the_repository->index,\n+\t\t\t\t\t\tone_shot, quiet, argv[i],\n+\t\t\t\t\t\tmerge_one_file, &data);\n \n-\tif (err && !quiet)\n-\t\tdie(_(\"merge program failed\"));\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 00000000000..30691fccd77\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,87 @@\n+#include \"cache.h\"\n+#include \"merge-strategies.h\"\n+\n+static int merge_entry(struct index_state *istate, unsigned int pos,\n+\t\t       const char *path, int *err, merge_index_fn fn,\n+\t\t       void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = { 0 };\n+\tunsigned int modes[3] = { 0 };\n+\n+\t*err = 0;\n+\n+\tif (pos >= istate->cache_nr)\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n+\n+\tif (fn(istate, oids[0], oids[1], oids[2], path, modes[0], modes[1],\n+\t       modes[2], data))\n+\t\t(*err)++;\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_index_fn fn, void *data)\n+{\n+\tint err, ret;\n+\tint pos = index_name_pos(istate, path, strlen(path));\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos >= 0)\n+\t\treturn 0;\n+\n+\tret = merge_entry(istate, -pos - 1, path, &err, fn, data);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\tif (err) {\n+\t\tif (!quiet && !oneshot)\n+\t\t\tdie(_(\"merge program failed\"));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_index_fn fn, void *data)\n+{\n+\tint err, ret;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t\telse if (ret > 0)\n+\t\t\ti += ret - 1;\n+\n+\t\tif (err && !oneshot) {\n+\t\t\tif (!quiet)\n+\t\t\t\tdie(_(\"merge program failed\"));\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\tif (err && !quiet)\n+\t\tdie(_(\"merge program failed\"));\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 00000000000..cee9168a046\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,19 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+struct object_id;\n+struct index_state;\n+typedef int (*merge_index_fn)(struct index_state *istate,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob,\n+\t\t\t      const char *path, unsigned int orig_mode,\n+\t\t\t      unsigned int our_mode, unsigned int their_mode,\n+\t\t\t      void *data);\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_index_fn fn, void *data);\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_index_fn fn, void *data);\n+\n+#endif /* MERGE_STRATEGIES_H */\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467492","messageId":"patch-v9-09.12-f29343197eb-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 09/12] builtin/merge-index.c: don't USE_THE_INDEX_COMPATIBILITY_MACROS","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:26Z","receivedAt":"2022-11-18T11:19:12Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove \"USE_THE_INDEX_COMPATIBILITY_MACROS\" and instead pass\n\"the_index\" around between the functions in this file. In a subsequent\ncommit we'll libify this, and don't want to use\n\"USE_THE_INDEX_COMPATIBILITY_MACROS\" in any more places in the\ntop-level *.c files. Doing this first makes that diff a lot smaller.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 31 +++++++++++++++----------------\n 1 file changed, 15 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 9bffcc5b0f1..c269d76cc8f 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_INDEX_COMPATIBILITY_MACROS\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"run-command.h\"\n@@ -7,7 +6,7 @@ static const char *pgm;\n static int one_shot, quiet;\n static int err;\n \n-static int merge_entry(int pos, const char *path)\n+static int merge_entry(struct index_state *istate, int pos, const char *path)\n {\n \tint found;\n \tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n@@ -15,11 +14,11 @@ static int merge_entry(int pos, const char *path)\n \tchar ownbuf[4][60];\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\tif (pos >= active_nr)\n+\tif (pos >= istate->cache_nr)\n \t\tdie(_(\"'%s' is not in the cache\"), path);\n \tfound = 0;\n \tdo {\n-\t\tconst struct cache_entry *ce = active_cache[pos];\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n \t\tint stage = ce_stage(ce);\n \n \t\tif (strcmp(ce->name, path))\n@@ -29,7 +28,7 @@ static int merge_entry(int pos, const char *path)\n \t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n \t\targuments[stage] = hexbuf[stage];\n \t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < active_nr);\n+\t} while (++pos < istate->cache_nr);\n \tif (!found)\n \t\tdie(_(\"'%s' is not in the cache\"), path);\n \n@@ -46,27 +45,27 @@ static int merge_entry(int pos, const char *path)\n \treturn found;\n }\n \n-static void merge_one_path(const char *path)\n+static void merge_one_path(struct index_state *istate, const char *path)\n {\n-\tint pos = cache_name_pos(path, strlen(path));\n+\tint pos = index_name_pos(istate, path, strlen(path));\n \n \t/*\n \t * If it already exists in the cache as stage0, it's\n \t * already merged and there is nothing to do.\n \t */\n \tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n+\t\tmerge_entry(istate, -pos-1, path);\n }\n \n-static void merge_all(void)\n+static void merge_all(struct index_state *istate)\n {\n \tint i;\n \n-\tfor (i = 0; i < active_nr; i++) {\n-\t\tconst struct cache_entry *ce = active_cache[i];\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n \t\tif (!ce_stage(ce))\n \t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n+\t\ti += merge_entry(istate, i, ce->name)-1;\n \t}\n }\n \n@@ -116,16 +115,16 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n \t\t\t      usage, options);\n \n-\tread_cache();\n+\trepo_read_index(the_repository);\n \n \t/* TODO: audit for interaction with sparse-index. */\n-\tensure_full_index(&the_index);\n+\tensure_full_index(the_repository->index);\n \n \tif (all)\n-\t\tmerge_all();\n+\t\tmerge_all(the_repository->index);\n \telse\n \t\tfor (size_t i = 0; i < argc; i++)\n-\t\t\tmerge_one_path(argv[i]);\n+\t\t\tmerge_one_path(the_repository->index, argv[i]);\n \n \tif (err && !quiet)\n \t\tdie(_(\"merge program failed\"));\n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467493","messageId":"patch-v9-11.12-adb712ca7a5-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 11/12] merge-index: use \"struct strvec\" and helper to prepare args","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:28Z","receivedAt":"2022-11-18T11:19:15Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Refactor the code that was libified in the preceding commit to use\nstrvec_pushf() with a helper function, instead of in-place xsnprintf()\ncode that we generate with a macro.\n\nThis is less efficient in term of the number of allocations we do, but\nit's now much clearer what's going on. The logic is simply that we\nhave an argument list like:\n\n\t<merge-program> <oids> <path> <modes>\n\nWhere we always need either an OID/mode pair, or \"\". Now we'll add\nboth to their own strvec, which we then combine at the end.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 44 ++++++++++++++++++++++++++-----------------\n 1 file changed, 27 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 21598a52383..d679272391b 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -7,6 +7,18 @@ struct mofs_data {\n \tconst char *program;\n };\n \n+static void push_arg(struct strvec *oids, struct strvec *modes,\n+\t\t     const struct object_id *oid, const unsigned int mode)\n+{\n+\tif (oid) {\n+\t\tstrvec_push(oids, oid_to_hex(oid));\n+\t\tstrvec_pushf(modes, \"%06o\", mode);\n+\t} else {\n+\t\tstrvec_push(oids, \"\");\n+\t\tstrvec_push(modes, \"\");\n+\t}\n+}\n+\n static int merge_one_file(struct index_state *istate,\n \t\t\t  const struct object_id *orig_blob,\n \t\t\t  const struct object_id *our_blob,\n@@ -15,27 +27,25 @@ static int merge_one_file(struct index_state *istate,\n \t\t\t  unsigned int their_mode, void *data)\n {\n \tstruct mofs_data *d = data;\n-\tconst char *pgm = d->program;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\tint stage = 0;\n+\tconst char *program = d->program;\n+\tstruct strvec oids = STRVEC_INIT;\n+\tstruct strvec modes = STRVEC_INIT;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-#define ADD_MOF_ARG(oid, mode) \\\n-\tif ((oid)) { \\\n-\t\tstage++; \\\n-\t\toid_to_hex_r(hexbuf[stage], (oid)); \\\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%06o\", (mode)); \\\n-\t\targuments[stage] = hexbuf[stage]; \\\n-\t\targuments[stage + 4] = ownbuf[stage]; \\\n-\t}\n+\tstrvec_push(&cmd.args, program);\n+\n+\tpush_arg(&oids, &modes, orig_blob, orig_mode);\n+\tpush_arg(&oids, &modes, our_blob, our_mode);\n+\tpush_arg(&oids, &modes, their_blob, their_mode);\n+\n+\tstrvec_pushv(&cmd.args, oids.v);\n+\tstrvec_clear(&oids);\n+\n+\tstrvec_push(&cmd.args, path);\n \n-\tADD_MOF_ARG(orig_blob, orig_mode);\n-\tADD_MOF_ARG(our_blob, our_mode);\n-\tADD_MOF_ARG(their_blob, their_mode);\n+\tstrvec_pushv(&cmd.args, modes.v);\n+\tstrvec_clear(&modes);\n \n-\tstrvec_pushv(&cmd.args, arguments);\n \treturn run_command(&cmd);\n }\n \n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467494","messageId":"patch-v9-12.12-f0368560140-20221118T110058Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v9 12/12] merge-index: make the argument parsing sensible & simpler","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-18T11:18:29Z","receivedAt":"2022-11-18T11:19:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"In a preceding commit when we migrated to parse_options() we took\npains to be bug-for-bug compatible with the existing command-line\ninterface, if possible.\n\nI.e. we forbade forms like:\n\n\tgit merge-index -a <program>\n\tgit merge-index <program> <opts> -a\n\nBut allowed:\n\n\tgit merge-index <program> -a\n\tgit merge-index <opts> <program> -a\n\nAs the \"-a\" argument was considered be provided for the \"<program>\",\nbut not a part of \"<opts>\".\n\nWe don't really need this strictness, as we don't have two \"-a\"\noptions. It's much simpler to implement a schema where the first\nnon-option argument is the <program>, and the rest are the\n\"<file>...\". We only allow that rest if the \"-a\" option isn't\nsupplied.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c  | 28 ++++++++--------------------\n t/t6060-merge-index.sh | 12 +++++++++---\n 2 files changed, 17 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex d679272391b..d8b62e4f663 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -59,21 +59,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n \t\tNULL\n \t};\n-#define OPT__MERGE_INDEX_ALL(v) \\\n-\tOPT_BOOL('a', NULL, (v), \\\n-\t\t N_(\"merge all files in the index that need merging\"))\n \tstruct option options[] = {\n \t\tOPT_BOOL('o', NULL, &one_shot,\n \t\t\t N_(\"don't stop at the first failed merge\")),\n \t\tOPT__QUIET(&quiet, N_(\"be quiet\")),\n-\t\tOPT__MERGE_INDEX_ALL(&all), /* include \"-a\" to show it in \"-bh\" */\n+\t\tOPT_BOOL('a', NULL, &all,\n+\t\t\t N_(\"merge all files in the index that need merging\")),\n \t\tOPT_END(),\n \t};\n-\tstruct option options_prog[] = {\n-\t\tOPT__MERGE_INDEX_ALL(&all),\n-\t\tOPT_END(),\n-\t};\n-#undef OPT__MERGE_INDEX_ALL\n \tstruct mofs_data data = { 0 };\n \n \t/* Without this we cannot rely on waitpid() to tell\n@@ -81,20 +74,15 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t */\n \tsignal(SIGCHLD, SIG_DFL);\n \n-\tif (argc < 3)\n-\t\tusage_with_options(usage, options);\n-\n-\t/* Option parsing without <merge-program> options */\n-\targc = parse_options(argc, argv, prefix, options, usage,\n-\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n-\tif (all)\n-\t\tusage_msg_optf(_(\"'%s' option can only be provided after '<merge-program>'\"),\n-\t\t\t      usage, options, \"-a\");\n-\t/* <merge-program> and its options */\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n \tif (!argc)\n \t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n \tdata.program = argv[0];\n-\targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n+\targv++;\n+\targc--;\n+\tif (!argc && !all)\n+\t\tusage_msg_opt(_(\"need '-a' or '<file>...'\"),\n+\t\t\t      usage, options);\n \tif (argc && all)\n \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n \t\t\t      usage, options);\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex bc201a69552..4ff9ace7f73 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -22,7 +22,7 @@ test_expect_success 'usage: 2 arguments' '\n \n test_expect_success 'usage: -a before <program>' '\n \tcat >expect <<-\\EOF &&\n-\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n+\tfatal: '\\''-a'\\'' and '\\''<file>...'\\'' are mutually exclusive\n \tEOF\n \ttest_expect_code 129 git merge-index -a b program >out 2>actual.raw &&\n \tgrep \"^fatal:\" actual.raw >actual &&\n@@ -34,7 +34,7 @@ for opt in -q -o\n do\n \ttest_expect_success \"usage: $opt after -a\" '\n \t\tcat >expect <<-EOF &&\n-\t\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n+\t\tfatal: need a <merge-program> argument\n \t\tEOF\n \t\ttest_expect_code 129 git merge-index -a $opt >out 2>actual.raw &&\n \t\tgrep \"^fatal:\" actual.raw >actual &&\n@@ -43,7 +43,13 @@ do\n \t'\n \n \ttest_expect_success \"usage: $opt program\" '\n-\t\ttest_expect_code 0 git merge-index $opt program\n+\t\tcat >expect <<-EOF &&\n+\t\tfatal: need '\\''-a'\\'' or '\\''<file>...'\\''\n+\t\tEOF\n+\t\ttest_expect_code 129 git merge-index $opt program 2>actual.raw &&\n+\t\tgrep \"^fatal:\" actual.raw >actual &&\n+\t\ttest_must_be_empty out &&\n+\t\ttest_cmp expect actual\n \t'\n done\n \n-- \n2.38.0.1511.gcdcff1f1dc2\n\n"},{"id":"467543","messageId":"Y3gVekgT7jLibjWo@nand.local","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"Re: [PATCH v9 00/12] merge-index: prepare to rewrite merge drivers in C","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-11-18T23:30:02Z","receivedAt":"2022-11-19T00:04:03Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Fri, Nov 18, 2022 at 12:18:17PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> This is a prep series for a re-roll of Alban Gruin's series to rewrite\n> various merge drivers from *.sh to *.c, and being able to call those\n> in-process.\n\nThanks for resurrecting this topic. I couldn't quite tell what this was\nsupposed to be based on from your cover letter, but digging around your\nrepo, the best I could come up with was:\n\n    $ git log --oneline --first-parent --merges master.\n    00c0dd7b8a Merge branch 'ab/various-leak-fixes' into ab/merge-index-prep\n    dc39d4bbb4 Merge branch 'pw/rebase-no-reflog-action' into ab/merge-index-prep\n\nwhen queuing, which seemed to do the trick.\n\nIf that wasn't what you had intended, let me know. The series does not\napply as-is on top of 'master' (which is at eea7033409 (The twelfth\nbatch, 2022-11-14), at the time of writing).\n\nThanks,\nTaylor\n"},{"id":"467576","messageId":"221119.86o7t3ds49.gmgdl@evledraar.gmail.com","threadId":"53755","inReplyTo":"Y3gVekgT7jLibjWo@nand.local","subject":"Re: [PATCH v9 00/12] merge-index: prepare to rewrite merge drivers in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-19T12:46:13Z","receivedAt":"2022-11-19T12:51:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Nov 18 2022, Taylor Blau wrote:\n\n> On Fri, Nov 18, 2022 at 12:18:17PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>> This is a prep series for a re-roll of Alban Gruin's series to rewrite\n>> various merge drivers from *.sh to *.c, and being able to call those\n>> in-process.\n>\n> Thanks for resurrecting this topic. I couldn't quite tell what this was\n> supposed to be based on from your cover letter, but digging around your\n> repo, the best I could come up with was:\n>\n>     $ git log --oneline --first-parent --merges master.\n>     00c0dd7b8a Merge branch 'ab/various-leak-fixes' into ab/merge-index-prep\n>     dc39d4bbb4 Merge branch 'pw/rebase-no-reflog-action' into ab/merge-index-prep\n>\n> when queuing, which seemed to do the trick.\n\nYes, sorry. It completely slipped my mind to mention it, but it's on top\nof pw/rebase-no-reflog-action + ab/various-leak-fixes, except...\n\n> If that wasn't what you had intended, let me know. The series does not\n> apply as-is on top of 'master' (which is at eea7033409 (The twelfth\n> batch, 2022-11-14), at the time of writing).\n\n...just applying it on ab/various-leak-fixes won't *quite* do it, it'll\nalso need the more recent \"master\", namely the now-landed\nrs/no-more-run-command-v.\n"},{"id":"469068","messageId":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com","subject":"[PATCH v10 00/12] merge-index: prepare to rewrite merge drivers in C","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:04Z","receivedAt":"2022-12-15T08:52:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This is a prep series for a re-roll of Alban Gruin's series to rewrite\nvarious merge drivers from *.sh to *.c, and being able to call those\nin-process.\n\nThat series was discussed on-list in August[1], and has now been\nejected from \"seen\" due to staleness. This v10 re-roll is my second\nattempt at re-starting this topic, see [2] for v9.\n\nIn v8 there were concerns with the later part of this topic, but the\nparts that are included here weren't controversial, those will be part\n2 (and I think I've addressed those concerns).\n\nChanges since v9:\n\n* Rebase on minor (unrelated) merge-index and\n  \"USE_THE_INDEX_COMPATIBILITY_MACROS\" changes that have since landed.\n\n* Trivial adjustments to error messages, including marking one that\n  wasn't marked with _() for translation.\n\nSee [3] for my branch for this topic, which includes passing CI.\n\n1. https://lore.kernel.org/git/20220809185429.20098-9-alban.gruin@gmail.com/\n2. https://lore.kernel.org/git/cover-v9-00.12-00000000000-20221118T110058Z-avarab@gmail.com/\n3. https://github.com/avar/git/tree/ag/merge-strategies-in-c-prep-3\n\nAlban Gruin (4):\n  t6060: modify multiple files to expose a possible issue with\n    merge-index\n  t6060: add tests for removed files\n  merge-index: improve die() error messages\n  merge-index: libify merge_one_path() and merge_all()\n\nÆvar Arnfjörð Bjarmason (8):\n  merge-index doc & -h: fix padding, labels and \"()\" use\n  merge-index tests: add usage tests\n  merge-index: migrate to parse_options() API\n  merge-index i18n: mark die() messages for translation\n  merge-index: stop calling ensure_full_index() twice\n  builtin/merge-index.c: don't USE_THE_INDEX_VARIABLE\n  merge-index: use \"struct strvec\" and helper to prepare args\n  merge-index: make the argument parsing sensible & simpler\n\n Documentation/git-merge-index.txt |   2 +-\n Makefile                          |   1 +\n builtin/merge-index.c             | 167 ++++++++++++++----------------\n git.c                             |   2 +-\n merge-strategies.c                |  87 ++++++++++++++++\n merge-strategies.h                |  19 ++++\n t/t0450/txt-help-mismatches       |   1 -\n t/t6060-merge-index.sh            |  65 +++++++++++-\n 8 files changed, 249 insertions(+), 95 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\nRange-diff against v9:\n 1:  660b1242707 !  1:  9240ab10649 merge-index doc & -h: fix padding, labels and \"()\" use\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n     -\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n     +\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\");\n      \n    - \tread_cache();\n    + \trepo_read_index(the_repository);\n      \n     \n      ## t/t0450/txt-help-mismatches ##\n 2:  caf4a3790c4 =  2:  de36b52286b t6060: modify multiple files to expose a possible issue with merge-index\n 3:  d659ac983f8 =  3:  5edc8132329 t6060: add tests for removed files\n 4:  7c5b7c36411 =  4:  aa731011e0a merge-index tests: add usage tests\n 5:  07f6936011a !  5:  a3f69564ac5 merge-index: migrate to parse_options() API\n    @@ Commit message\n     \n      ## builtin/merge-index.c ##\n     @@\n    - #define USE_THE_INDEX_COMPATIBILITY_MACROS\n    + #define USE_THE_INDEX_VARIABLE\n      #include \"builtin.h\"\n     +#include \"parse-options.h\"\n      #include \"run-command.h\"\n    @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const ch\n     +\t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n     +\t\t\t      usage, options);\n      \n    - \tread_cache();\n    + \trepo_read_index(the_repository);\n      \n      \t/* TODO: audit for interaction with sparse-index. */\n      \tensure_full_index(&the_index);\n 6:  8d6cfd4bacc !  6:  324368401a2 merge-index: improve die() error messages\n    @@ builtin/merge-index.c\n     @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      \tstruct child_process cmd = CHILD_PROCESS_INIT;\n      \n    - \tif (pos >= active_nr)\n    + \tif (pos >= the_index.cache_nr)\n     -\t\tdie(\"git merge-index: %s not in the cache\", path);\n     +\t\tdie(\"'%s' is not in the cache\", path);\n      \tfound = 0;\n      \tdo {\n    - \t\tconst struct cache_entry *ce = active_cache[pos];\n    + \t\tconst struct cache_entry *ce = the_index.cache[pos];\n     @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      \t\targuments[stage + 4] = ownbuf[stage];\n    - \t} while (++pos < active_nr);\n    + \t} while (++pos < the_index.cache_nr);\n      \tif (!found)\n     -\t\tdie(\"git merge-index: %s not in the cache\", path);\n     +\t\tdie(\"'%s' is not in the cache\", path);\n 7:  62c5fd4faaa !  7:  de4d11798db merge-index i18n: mark die() messages for translation\n    @@ builtin/merge-index.c\n     @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      \tstruct child_process cmd = CHILD_PROCESS_INIT;\n      \n    - \tif (pos >= active_nr)\n    + \tif (pos >= the_index.cache_nr)\n     -\t\tdie(\"'%s' is not in the cache\", path);\n     +\t\tdie(_(\"'%s' is not in the cache\"), path);\n      \tfound = 0;\n      \tdo {\n    - \t\tconst struct cache_entry *ce = active_cache[pos];\n    + \t\tconst struct cache_entry *ce = the_index.cache[pos];\n     @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      \t\targuments[stage + 4] = ownbuf[stage];\n    - \t} while (++pos < active_nr);\n    + \t} while (++pos < the_index.cache_nr);\n      \tif (!found)\n     -\t\tdie(\"'%s' is not in the cache\", path);\n     +\t\tdie(_(\"'%s' is not in the cache\"), path);\n 8:  e44d58a505a !  8:  45cf7995448 merge-index: stop calling ensure_full_index() twice\n    @@ builtin/merge-index.c: static void merge_one_path(const char *path)\n     -\t/* TODO: audit for interaction with sparse-index. */\n     -\tensure_full_index(&the_index);\n     +\n    - \tfor (i = 0; i < active_nr; i++) {\n    - \t\tconst struct cache_entry *ce = active_cache[i];\n    + \tfor (i = 0; i < the_index.cache_nr; i++) {\n    + \t\tconst struct cache_entry *ce = the_index.cache[i];\n      \t\tif (!ce_stage(ce))\n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n      \t/* TODO: audit for interaction with sparse-index. */\n 9:  1f7c941035d !  9:  fc9a05ee034 builtin/merge-index.c: don't USE_THE_INDEX_COMPATIBILITY_MACROS\n    @@ Metadata\n     Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## Commit message ##\n    -    builtin/merge-index.c: don't USE_THE_INDEX_COMPATIBILITY_MACROS\n    +    builtin/merge-index.c: don't USE_THE_INDEX_VARIABLE\n     \n    -    Remove \"USE_THE_INDEX_COMPATIBILITY_MACROS\" and instead pass\n    -    \"the_index\" around between the functions in this file. In a subsequent\n    -    commit we'll libify this, and don't want to use\n    -    \"USE_THE_INDEX_COMPATIBILITY_MACROS\" in any more places in the\n    -    top-level *.c files. Doing this first makes that diff a lot smaller.\n    +    Remove \"USE_THE_INDEX_VARIABLE\" and instead pass \"the_index\" around\n    +    between the functions in this file. In a subsequent commit we'll\n    +    libify this, and don't want to use \"USE_THE_INDEX_VARIABLE\" in any\n    +    more places in the top-level *.c files. Doing this first makes that\n    +    diff a lot smaller.\n     \n         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n     \n      ## builtin/merge-index.c ##\n     @@\n    --#define USE_THE_INDEX_COMPATIBILITY_MACROS\n    +-#define USE_THE_INDEX_VARIABLE\n      #include \"builtin.h\"\n      #include \"parse-options.h\"\n      #include \"run-command.h\"\n    @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      \tchar ownbuf[4][60];\n      \tstruct child_process cmd = CHILD_PROCESS_INIT;\n      \n    --\tif (pos >= active_nr)\n    +-\tif (pos >= the_index.cache_nr)\n     +\tif (pos >= istate->cache_nr)\n      \t\tdie(_(\"'%s' is not in the cache\"), path);\n      \tfound = 0;\n      \tdo {\n    --\t\tconst struct cache_entry *ce = active_cache[pos];\n    +-\t\tconst struct cache_entry *ce = the_index.cache[pos];\n     +\t\tconst struct cache_entry *ce = istate->cache[pos];\n      \t\tint stage = ce_stage(ce);\n      \n    @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      \t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n      \t\targuments[stage] = hexbuf[stage];\n      \t\targuments[stage + 4] = ownbuf[stage];\n    --\t} while (++pos < active_nr);\n    +-\t} while (++pos < the_index.cache_nr);\n     +\t} while (++pos < istate->cache_nr);\n      \tif (!found)\n      \t\tdie(_(\"'%s' is not in the cache\"), path);\n    @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n     -static void merge_one_path(const char *path)\n     +static void merge_one_path(struct index_state *istate, const char *path)\n      {\n    --\tint pos = cache_name_pos(path, strlen(path));\n    +-\tint pos = index_name_pos(&the_index, path, strlen(path));\n     +\tint pos = index_name_pos(istate, path, strlen(path));\n      \n      \t/*\n    @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      {\n      \tint i;\n      \n    --\tfor (i = 0; i < active_nr; i++) {\n    --\t\tconst struct cache_entry *ce = active_cache[i];\n    +-\tfor (i = 0; i < the_index.cache_nr; i++) {\n    +-\t\tconst struct cache_entry *ce = the_index.cache[i];\n     +\tfor (i = 0; i < istate->cache_nr; i++) {\n     +\t\tconst struct cache_entry *ce = istate->cache[i];\n      \t\tif (!ce_stage(ce))\n    @@ builtin/merge-index.c: static int merge_entry(int pos, const char *path)\n      }\n      \n     @@ builtin/merge-index.c: int cmd_merge_index(int argc, const char **argv, const char *prefix)\n    - \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n    - \t\t\t      usage, options);\n    - \n    --\tread_cache();\n    -+\trepo_read_index(the_repository);\n    + \trepo_read_index(the_repository);\n      \n      \t/* TODO: audit for interaction with sparse-index. */\n     -\tensure_full_index(&the_index);\n10:  8c43b64dec4 = 10:  0efc5039e46 merge-index: libify merge_one_path() and merge_all()\n11:  592db883dad = 11:  748fef4434f merge-index: use \"struct strvec\" and helper to prepare args\n12:  5a2c4dd3acf = 12:  40b6d296f3a merge-index: make the argument parsing sensible & simpler\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469069","messageId":"patch-v10-01.12-9240ab10649-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 01/12] merge-index doc & -h: fix padding, labels and \"()\" use","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:05Z","receivedAt":"2022-12-15T08:52:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Make the \"merge-index\" doc SYNOPSIS and \"-h\" output consistent with\none another, and small issues with it:\n\n- Whitespace padding, per e2f4e7e8c0f (doc txt & -h consistency:\n  correct padding around \"[]()\", 2022-10-13).\n\n- Use \"<file>\" consistently, rather than using \"<filename>\" in the\n  \"-h\" output, and \"<file>\" in the SYNOPSIS.\n\n- The \"-h\" version incorrectly claimed that the filename was optional,\n  but it's not.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Documentation/git-merge-index.txt | 2 +-\n builtin/merge-index.c             | 2 +-\n t/t0450/txt-help-mismatches       | 1 -\n 3 files changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt\nindex eea56b3154e..a297105d6d8 100644\n--- a/Documentation/git-merge-index.txt\n+++ b/Documentation/git-merge-index.txt\n@@ -9,7 +9,7 @@ git-merge-index - Run a merge for files needing merging\n SYNOPSIS\n --------\n [verse]\n-'git merge-index' [-o] [-q] <merge-program> (-a | ( [--] <file>...) )\n+'git merge-index' [-o] [-q] <merge-program> (-a | ([--] <file>...))\n \n DESCRIPTION\n -----------\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 452f833ac46..69b18ed82ac 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -80,7 +80,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tsignal(SIGCHLD, SIG_DFL);\n \n \tif (argc < 3)\n-\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])\");\n+\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\");\n \n \trepo_read_index(the_repository);\n \ndiff --git a/t/t0450/txt-help-mismatches b/t/t0450/txt-help-mismatches\nindex a0777acd667..9e73c1892ae 100644\n--- a/t/t0450/txt-help-mismatches\n+++ b/t/t0450/txt-help-mismatches\n@@ -34,7 +34,6 @@ mailsplit\n maintenance\n merge\n merge-file\n-merge-index\n merge-one-file\n multi-pack-index\n name-rev\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469070","messageId":"patch-v10-02.12-de36b52286b-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 02/12] t6060: modify multiple files to expose a possible issue with merge-index","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:06Z","receivedAt":"2022-12-15T08:52:35Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nCurrently, merge-index iterates over every index entry, skipping stage0\nentries.  It will then count how many entries following the current one\nhave the same name, then fork to do the merge.  It will then increase\nthe iterator by the number of entries to skip them.  This behaviour is\ncorrect, as even if the subprocess modifies the index, merge-index does\nnot reload it at all.\n\nBut when it will be rewritten to use a function, the index it will use\nwill be modified and may shrink when a conflict happens or if a file is\nremoved, so we have to be careful to handle such cases.\n\nHere is an example:\n\n *    Merge branches, file1 and file2 are trivially mergeable.\n |\\\n | *  Modifies file1 and file2.\n * |  Modifies file1 and file2.\n |/\n *    Adds file1 and file2.\n\nWhen the merge happens, the index will look like that:\n\n i -> 0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n      3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\nmerge-index handles `file1' first.  As it appears 3 times after the\niterator, it is merged.  The index is now stale, `i' is increased by 3,\nand the index now looks like this:\n\n      0. file1 (stage1)\n      1. file1 (stage2)\n      2. file1 (stage3)\n i -> 3. file2 (stage1)\n      4. file2 (stage2)\n      5. file2 (stage3)\n\n`file2' appears three times too, so it is merged.\n\nWith a naive rewrite, the index would look like this:\n\n      0. file1 (stage0)\n      1. file2 (stage1)\n      2. file2 (stage2)\n i -> 3. file2 (stage3)\n\n`file2' appears once at the iterator or after, so it will be added,\n_not_ merged.  Which is wrong.\n\nA naive rewrite would lead to unproperly merged files, or even files not\nhandled at all.\n\nThis changes t6060 to reproduce this case, by creating 2 files instead\nof 1, to check the correctness of the soon-to-be-rewritten merge-index.\nThe files are identical, which is not really important -- the factors\nthat could trigger this issue are that they should be separated by at\nmost one entry in the index, and that the first one in the index should\nbe trivially mergeable.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t6060-merge-index.sh | 10 ++++++++--\n 1 file changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 1a8b64cce18..30513351c23 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -7,16 +7,19 @@ TEST_PASSES_SANITIZE_LEAK=true\n \n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n-\tgit add file &&\n+\tcp file file2 &&\n+\tgit add file file2 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n \tsed s/10/ten/ <file >tmp &&\n \tmv tmp file &&\n+\tcp file file2 &&\n \tgit commit -a -m ten &&\n \tgit tag ten\n '\n@@ -35,8 +38,11 @@ ten\n EOF\n \n test_expect_success 'read-tree does not resolve content merge' '\n+\tcat >expect <<-\\EOF &&\n+\tfile\n+\tfile2\n+\tEOF\n \tgit read-tree -i -m base ten two &&\n-\techo file >expect &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n \ttest_cmp expect unmerged\n '\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469071","messageId":"patch-v10-03.12-5edc8132329-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 03/12] t6060: add tests for removed files","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:07Z","receivedAt":"2022-12-15T08:52:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nUntil now, t6060 did not not check git-merge-one-file's behaviour when a\nfile is deleted in a branch.  To avoid regressions on this during the\nconversion from shell to C, this adds a new file, `file3', in the commit\ntagged as `base', and deletes it in the commit tagged as `two'.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t6060-merge-index.sh | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 30513351c23..079151ee06d 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -8,12 +8,14 @@ TEST_PASSES_SANITIZE_LEAK=true\n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n \tcp file file2 &&\n-\tgit add file file2 &&\n+\tcp file file3 &&\n+\tgit add file file2 file3 &&\n \tgit commit -m base &&\n \tgit tag base &&\n \tsed s/2/two/ <file >tmp &&\n \tmv tmp file &&\n \tcp file file2 &&\n+\tgit rm file3 &&\n \tgit commit -a -m two &&\n \tgit tag two &&\n \tgit checkout -b other HEAD^ &&\n@@ -41,6 +43,7 @@ test_expect_success 'read-tree does not resolve content merge' '\n \tcat >expect <<-\\EOF &&\n \tfile\n \tfile2\n+\tfile3\n \tEOF\n \tgit read-tree -i -m base ten two &&\n \tgit diff-files --name-only --diff-filter=U >unmerged &&\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469072","messageId":"patch-v10-04.12-aa731011e0a-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 04/12] merge-index tests: add usage tests","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:08Z","receivedAt":"2022-12-15T08:52:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add tests that stress the current behavior of the options parsing in\ncmd_merge_index(), in preparation for moving it over to\nparse_options().\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t6060-merge-index.sh | 44 ++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 44 insertions(+)\n\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 079151ee06d..edc03b41ab9 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -5,6 +5,50 @@ test_description='basic git merge-index / git-merge-one-file tests'\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n+test_expect_success 'usage: 1 argument' '\n+\ttest_expect_code 129 git merge-index a >out 2>err &&\n+\ttest_must_be_empty out &&\n+\tgrep ^usage err\n+'\n+\n+test_expect_success 'usage: 2 arguments' '\n+\tcat >expect <<-\\EOF &&\n+\tfatal: git merge-index: b not in the cache\n+\tEOF\n+\ttest_expect_code 128 git merge-index a b >out 2>actual &&\n+\ttest_must_be_empty out &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'usage: -a before <program>' '\n+\tcat >expect <<-\\EOF &&\n+\tfatal: git merge-index: b not in the cache\n+\tEOF\n+\ttest_expect_code 128 git merge-index -a b program >out 2>actual &&\n+\ttest_must_be_empty out &&\n+\ttest_cmp expect actual\n+'\n+\n+for opt in -q -o\n+do\n+\ttest_expect_success \"usage: $opt after -a\" '\n+\t\tcat >expect <<-EOF &&\n+\t\tfatal: git merge-index: unknown option $opt\n+\t\tEOF\n+\t\ttest_expect_code 128 git merge-index -a $opt >out 2>actual &&\n+\t\ttest_must_be_empty out &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"usage: $opt program\" '\n+\t\ttest_expect_code 0 git merge-index $opt program\n+\t'\n+done\n+\n+test_expect_success 'usage: program' '\n+\ttest_expect_code 129 git merge-index program\n+'\n+\n test_expect_success 'setup diverging branches' '\n \ttest_write_lines 1 2 3 4 5 6 7 8 9 10 >file &&\n \tcp file file2 &&\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469073","messageId":"patch-v10-05.12-a3f69564ac5-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 05/12] merge-index: migrate to parse_options() API","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:09Z","receivedAt":"2022-12-15T08:52:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Migrate the \"merge-index\" command to the parse_options() API, a\npreceding commit added tests for the existing behavior.\n\nIn a subsequent commit we'll adjust the behavior to be more consistent\nwith how most other commands work, but for now let's take pains to\npreserve it as-is. We need to e.g. call parse_options() twice now, as\nthe \"-a\" option is currently only understood after \"<merge-program>\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c  | 71 ++++++++++++++++++++++++++----------------\n git.c                  |  2 +-\n t/t6060-merge-index.sh | 10 +++---\n 3 files changed, 51 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 69b18ed82ac..3855531c579 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,5 +1,6 @@\n #define USE_THE_INDEX_VARIABLE\n #include \"builtin.h\"\n+#include \"parse-options.h\"\n #include \"run-command.h\"\n \n static const char *pgm;\n@@ -72,7 +73,26 @@ static void merge_all(void)\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n-\tint i, force_file = 0;\n+\tint all = 0;\n+\tconst char * const usage[] = {\n+\t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n+\t\tNULL\n+\t};\n+#define OPT__MERGE_INDEX_ALL(v) \\\n+\tOPT_BOOL('a', NULL, (v), \\\n+\t\t N_(\"merge all files in the index that need merging\"))\n+\tstruct option options[] = {\n+\t\tOPT_BOOL('o', NULL, &one_shot,\n+\t\t\t N_(\"don't stop at the first failed merge\")),\n+\t\tOPT__QUIET(&quiet, N_(\"be quiet\")),\n+\t\tOPT__MERGE_INDEX_ALL(&all), /* include \"-a\" to show it in \"-bh\" */\n+\t\tOPT_END(),\n+\t};\n+\tstruct option options_prog[] = {\n+\t\tOPT__MERGE_INDEX_ALL(&all),\n+\t\tOPT_END(),\n+\t};\n+#undef OPT__MERGE_INDEX_ALL\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -80,38 +100,35 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tsignal(SIGCHLD, SIG_DFL);\n \n \tif (argc < 3)\n-\t\tusage(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\");\n+\t\tusage_with_options(usage, options);\n+\n+\t/* Option parsing without <merge-program> options */\n+\targc = parse_options(argc, argv, prefix, options, usage,\n+\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n+\tif (all)\n+\t\tusage_msg_optf(_(\"'%s' option can only be provided after '<merge-program>'\"),\n+\t\t\t      usage, options, \"-a\");\n+\t/* <merge-program> and its options */\n+\tif (!argc)\n+\t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n+\tpgm = argv[0];\n+\targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n+\tif (argc && all)\n+\t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n+\t\t\t      usage, options);\n \n \trepo_read_index(the_repository);\n \n \t/* TODO: audit for interaction with sparse-index. */\n \tensure_full_index(&the_index);\n \n-\ti = 1;\n-\tif (!strcmp(argv[i], \"-o\")) {\n-\t\tone_shot = 1;\n-\t\ti++;\n-\t}\n-\tif (!strcmp(argv[i], \"-q\")) {\n-\t\tquiet = 1;\n-\t\ti++;\n-\t}\n-\tpgm = argv[i++];\n-\tfor (; i < argc; i++) {\n-\t\tconst char *arg = argv[i];\n-\t\tif (!force_file && *arg == '-') {\n-\t\t\tif (!strcmp(arg, \"--\")) {\n-\t\t\t\tforce_file = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tif (!strcmp(arg, \"-a\")) {\n-\t\t\t\tmerge_all();\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tdie(\"git merge-index: unknown option %s\", arg);\n-\t\t}\n-\t\tmerge_one_path(arg);\n-\t}\n+\n+\tif (all)\n+\t\tmerge_all();\n+\telse\n+\t\tfor (size_t i = 0; i < argc; i++)\n+\t\t\tmerge_one_path(argv[i]);\n+\n \tif (err && !quiet)\n \t\tdie(\"merge program failed\");\n \treturn err;\ndiff --git a/git.c b/git.c\nindex 277a8cce840..557a33925e3 100644\n--- a/git.c\n+++ b/git.c\n@@ -560,7 +560,7 @@ static struct cmd_struct commands[] = {\n \t{ \"merge\", cmd_merge, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"merge-base\", cmd_merge_base, RUN_SETUP },\n \t{ \"merge-file\", cmd_merge_file, RUN_SETUP_GENTLY },\n-\t{ \"merge-index\", cmd_merge_index, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"merge-index\", cmd_merge_index, RUN_SETUP },\n \t{ \"merge-ours\", cmd_merge_ours, RUN_SETUP | NO_PARSEOPT },\n \t{ \"merge-recursive\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\n \t{ \"merge-recursive-ours\", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex edc03b41ab9..6c59e7bc4e5 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -22,9 +22,10 @@ test_expect_success 'usage: 2 arguments' '\n \n test_expect_success 'usage: -a before <program>' '\n \tcat >expect <<-\\EOF &&\n-\tfatal: git merge-index: b not in the cache\n+\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n \tEOF\n-\ttest_expect_code 128 git merge-index -a b program >out 2>actual &&\n+\ttest_expect_code 129 git merge-index -a b program >out 2>actual.raw &&\n+\tgrep \"^fatal:\" actual.raw >actual &&\n \ttest_must_be_empty out &&\n \ttest_cmp expect actual\n '\n@@ -33,9 +34,10 @@ for opt in -q -o\n do\n \ttest_expect_success \"usage: $opt after -a\" '\n \t\tcat >expect <<-EOF &&\n-\t\tfatal: git merge-index: unknown option $opt\n+\t\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n \t\tEOF\n-\t\ttest_expect_code 128 git merge-index -a $opt >out 2>actual &&\n+\t\ttest_expect_code 129 git merge-index -a $opt >out 2>actual.raw &&\n+\t\tgrep \"^fatal:\" actual.raw >actual &&\n \t\ttest_must_be_empty out &&\n \t\ttest_cmp expect actual\n \t'\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469074","messageId":"patch-v10-06.12-324368401a2-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 06/12] merge-index: improve die() error messages","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:10Z","receivedAt":"2022-12-15T08:52:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nOur usual convention is not to repeat the program name back at the\nuser, and to quote path arguments. Let's do that now to reduce the\nsize of the subsequent commit.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c  | 4 ++--\n t/t6060-merge-index.sh | 2 +-\n 2 files changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 3855531c579..2dc789fb787 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path)\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \tif (pos >= the_index.cache_nr)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n+\t\tdie(\"'%s' is not in the cache\", path);\n \tfound = 0;\n \tdo {\n \t\tconst struct cache_entry *ce = the_index.cache[pos];\n@@ -31,7 +31,7 @@ static int merge_entry(int pos, const char *path)\n \t\targuments[stage + 4] = ownbuf[stage];\n \t} while (++pos < the_index.cache_nr);\n \tif (!found)\n-\t\tdie(\"git merge-index: %s not in the cache\", path);\n+\t\tdie(\"'%s' is not in the cache\", path);\n \n \tstrvec_pushv(&cmd.args, arguments);\n \tif (run_command(&cmd)) {\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex 6c59e7bc4e5..bc201a69552 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -13,7 +13,7 @@ test_expect_success 'usage: 1 argument' '\n \n test_expect_success 'usage: 2 arguments' '\n \tcat >expect <<-\\EOF &&\n-\tfatal: git merge-index: b not in the cache\n+\tfatal: '\\''b'\\'' is not in the cache\n \tEOF\n \ttest_expect_code 128 git merge-index a b >out 2>actual &&\n \ttest_must_be_empty out &&\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469075","messageId":"patch-v10-07.12-de4d11798db-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 07/12] merge-index i18n: mark die() messages for translation","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:11Z","receivedAt":"2022-12-15T08:52:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Mark the die() messages for translation with _(). We don't rely on the\nspecifics of these messages as plumbing, so they can be safely\ntranslated.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 2dc789fb787..4d91e7ea122 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -16,7 +16,7 @@ static int merge_entry(int pos, const char *path)\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n \tif (pos >= the_index.cache_nr)\n-\t\tdie(\"'%s' is not in the cache\", path);\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n \tfound = 0;\n \tdo {\n \t\tconst struct cache_entry *ce = the_index.cache[pos];\n@@ -31,7 +31,7 @@ static int merge_entry(int pos, const char *path)\n \t\targuments[stage + 4] = ownbuf[stage];\n \t} while (++pos < the_index.cache_nr);\n \tif (!found)\n-\t\tdie(\"'%s' is not in the cache\", path);\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n \n \tstrvec_pushv(&cmd.args, arguments);\n \tif (run_command(&cmd)) {\n@@ -39,7 +39,7 @@ static int merge_entry(int pos, const char *path)\n \t\t\terr++;\n \t\telse {\n \t\t\tif (!quiet)\n-\t\t\t\tdie(\"merge program failed\");\n+\t\t\t\tdie(_(\"merge program failed\"));\n \t\t\texit(1);\n \t\t}\n \t}\n@@ -130,6 +130,6 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\t\tmerge_one_path(argv[i]);\n \n \tif (err && !quiet)\n-\t\tdie(\"merge program failed\");\n+\t\tdie(_(\"merge program failed\"));\n \treturn err;\n }\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469076","messageId":"patch-v10-08.12-45cf7995448-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 08/12] merge-index: stop calling ensure_full_index() twice","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:12Z","receivedAt":"2022-12-15T08:52:57Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"When most of the ensure_full_index() calls were added in\n8e97852919f (Merge branch 'ds/sparse-index-protections', 2021-04-30)\nwe could add them at the start of cmd_*() for built-ins, but in some\ncases we couldn't do that, as we'd only want to initialize the index\nconditionally on some branches in the code.\n\nBut this code added in 299e2c4561b (merge-index: ensure full index,\n2021-04-01) (part of 8e97852919f) isn't such a case. The merge_all()\nfunction is only called by cmd_merge_index(), which before calling it\nwill have called ensure_full_index() unconditionally.\n\nWe can therefore skip this. While we're at it, and mainly so that\nwe'll see the relevant code in the context, let's fix a minor\nwhitespace issue that the addition of the ensure_full_index() call in\n299e2c4561b introduced.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 4d91e7ea122..cd160779cbf 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -61,8 +61,7 @@ static void merge_one_path(const char *path)\n static void merge_all(void)\n {\n \tint i;\n-\t/* TODO: audit for interaction with sparse-index. */\n-\tensure_full_index(&the_index);\n+\n \tfor (i = 0; i < the_index.cache_nr; i++) {\n \t\tconst struct cache_entry *ce = the_index.cache[i];\n \t\tif (!ce_stage(ce))\n@@ -122,7 +121,6 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t/* TODO: audit for interaction with sparse-index. */\n \tensure_full_index(&the_index);\n \n-\n \tif (all)\n \t\tmerge_all();\n \telse\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469077","messageId":"patch-v10-09.12-fc9a05ee034-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 09/12] builtin/merge-index.c: don't USE_THE_INDEX_VARIABLE","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:13Z","receivedAt":"2022-12-15T08:53:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Remove \"USE_THE_INDEX_VARIABLE\" and instead pass \"the_index\" around\nbetween the functions in this file. In a subsequent commit we'll\nlibify this, and don't want to use \"USE_THE_INDEX_VARIABLE\" in any\nmore places in the top-level *.c files. Doing this first makes that\ndiff a lot smaller.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 29 ++++++++++++++---------------\n 1 file changed, 14 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex cd160779cbf..c269d76cc8f 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,4 +1,3 @@\n-#define USE_THE_INDEX_VARIABLE\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"run-command.h\"\n@@ -7,7 +6,7 @@ static const char *pgm;\n static int one_shot, quiet;\n static int err;\n \n-static int merge_entry(int pos, const char *path)\n+static int merge_entry(struct index_state *istate, int pos, const char *path)\n {\n \tint found;\n \tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n@@ -15,11 +14,11 @@ static int merge_entry(int pos, const char *path)\n \tchar ownbuf[4][60];\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\tif (pos >= the_index.cache_nr)\n+\tif (pos >= istate->cache_nr)\n \t\tdie(_(\"'%s' is not in the cache\"), path);\n \tfound = 0;\n \tdo {\n-\t\tconst struct cache_entry *ce = the_index.cache[pos];\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n \t\tint stage = ce_stage(ce);\n \n \t\tif (strcmp(ce->name, path))\n@@ -29,7 +28,7 @@ static int merge_entry(int pos, const char *path)\n \t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n \t\targuments[stage] = hexbuf[stage];\n \t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < the_index.cache_nr);\n+\t} while (++pos < istate->cache_nr);\n \tif (!found)\n \t\tdie(_(\"'%s' is not in the cache\"), path);\n \n@@ -46,27 +45,27 @@ static int merge_entry(int pos, const char *path)\n \treturn found;\n }\n \n-static void merge_one_path(const char *path)\n+static void merge_one_path(struct index_state *istate, const char *path)\n {\n-\tint pos = index_name_pos(&the_index, path, strlen(path));\n+\tint pos = index_name_pos(istate, path, strlen(path));\n \n \t/*\n \t * If it already exists in the cache as stage0, it's\n \t * already merged and there is nothing to do.\n \t */\n \tif (pos < 0)\n-\t\tmerge_entry(-pos-1, path);\n+\t\tmerge_entry(istate, -pos-1, path);\n }\n \n-static void merge_all(void)\n+static void merge_all(struct index_state *istate)\n {\n \tint i;\n \n-\tfor (i = 0; i < the_index.cache_nr; i++) {\n-\t\tconst struct cache_entry *ce = the_index.cache[i];\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n \t\tif (!ce_stage(ce))\n \t\t\tcontinue;\n-\t\ti += merge_entry(i, ce->name)-1;\n+\t\ti += merge_entry(istate, i, ce->name)-1;\n \t}\n }\n \n@@ -119,13 +118,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \trepo_read_index(the_repository);\n \n \t/* TODO: audit for interaction with sparse-index. */\n-\tensure_full_index(&the_index);\n+\tensure_full_index(the_repository->index);\n \n \tif (all)\n-\t\tmerge_all();\n+\t\tmerge_all(the_repository->index);\n \telse\n \t\tfor (size_t i = 0; i < argc; i++)\n-\t\t\tmerge_one_path(argv[i]);\n+\t\t\tmerge_one_path(the_repository->index, argv[i]);\n \n \tif (err && !quiet)\n \t\tdie(_(\"merge program failed\"));\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469078","messageId":"patch-v10-10.12-0efc5039e46-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 10/12] merge-index: libify merge_one_path() and merge_all()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:14Z","receivedAt":"2022-12-15T08:53:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Alban Gruin <alban.gruin@gmail.com>\n\nMove the workhorse functions in \"builtin/merge-index.c\" into a new\n\"merge-strategies\" library, and mostly \"libify\" the code while doing\nso.\n\nEventually this will allow us to invoke merge strategies such as\n\"resolve\" and \"octopus\" in-process, once we've followed-up and\nreplaced \"git-merge-{resolve,octopus}.sh\" etc.\n\nBut for now let's move this code, while trying to optimize for as much\nof it as possible being highlighted by the diff rename detection.\n\nWe still call die() in this library. An earlier version of this[1]\nconverted these to \"error()\", but the problem with that that we'd then\npotentially run into the same error N times, e.g. once for every\n\"<file>\" we were asked to operate on, instead of dying on the first\ncase. So let's leave those to \"die()\" for now.\n\n1. https://lore.kernel.org/git/20220809185429.20098-4-alban.gruin@gmail.com/\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n Makefile              |  1 +\n builtin/merge-index.c | 95 ++++++++++++++++---------------------------\n merge-strategies.c    | 87 +++++++++++++++++++++++++++++++++++++++\n merge-strategies.h    | 19 +++++++++\n 4 files changed, 142 insertions(+), 60 deletions(-)\n create mode 100644 merge-strategies.c\n create mode 100644 merge-strategies.h\n\ndiff --git a/Makefile b/Makefile\nindex 0f7d7ab1fd2..6f4ac2e541d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1064,6 +1064,7 @@ LIB_OBJS += merge-blobs.o\n LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-ort-wrappers.o\n LIB_OBJS += merge-recursive.o\n+LIB_OBJS += merge-strategies.o\n LIB_OBJS += merge.o\n LIB_OBJS += midx.o\n LIB_OBJS += name-hash.o\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex c269d76cc8f..21598a52383 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -1,77 +1,50 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n+#include \"merge-strategies.h\"\n #include \"run-command.h\"\n \n-static const char *pgm;\n-static int one_shot, quiet;\n-static int err;\n+struct mofs_data {\n+\tconst char *program;\n+};\n \n-static int merge_entry(struct index_state *istate, int pos, const char *path)\n+static int merge_one_file(struct index_state *istate,\n+\t\t\t  const struct object_id *orig_blob,\n+\t\t\t  const struct object_id *our_blob,\n+\t\t\t  const struct object_id *their_blob, const char *path,\n+\t\t\t  unsigned int orig_mode, unsigned int our_mode,\n+\t\t\t  unsigned int their_mode, void *data)\n {\n-\tint found;\n+\tstruct mofs_data *d = data;\n+\tconst char *pgm = d->program;\n \tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n \tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n \tchar ownbuf[4][60];\n+\tint stage = 0;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-\tif (pos >= istate->cache_nr)\n-\t\tdie(_(\"'%s' is not in the cache\"), path);\n-\tfound = 0;\n-\tdo {\n-\t\tconst struct cache_entry *ce = istate->cache[pos];\n-\t\tint stage = ce_stage(ce);\n-\n-\t\tif (strcmp(ce->name, path))\n-\t\t\tbreak;\n-\t\tfound++;\n-\t\toid_to_hex_r(hexbuf[stage], &ce->oid);\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%o\", ce->ce_mode);\n-\t\targuments[stage] = hexbuf[stage];\n-\t\targuments[stage + 4] = ownbuf[stage];\n-\t} while (++pos < istate->cache_nr);\n-\tif (!found)\n-\t\tdie(_(\"'%s' is not in the cache\"), path);\n-\n-\tstrvec_pushv(&cmd.args, arguments);\n-\tif (run_command(&cmd)) {\n-\t\tif (one_shot)\n-\t\t\terr++;\n-\t\telse {\n-\t\t\tif (!quiet)\n-\t\t\t\tdie(_(\"merge program failed\"));\n-\t\t\texit(1);\n-\t\t}\n+#define ADD_MOF_ARG(oid, mode) \\\n+\tif ((oid)) { \\\n+\t\tstage++; \\\n+\t\toid_to_hex_r(hexbuf[stage], (oid)); \\\n+\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%06o\", (mode)); \\\n+\t\targuments[stage] = hexbuf[stage]; \\\n+\t\targuments[stage + 4] = ownbuf[stage]; \\\n \t}\n-\treturn found;\n-}\n-\n-static void merge_one_path(struct index_state *istate, const char *path)\n-{\n-\tint pos = index_name_pos(istate, path, strlen(path));\n \n-\t/*\n-\t * If it already exists in the cache as stage0, it's\n-\t * already merged and there is nothing to do.\n-\t */\n-\tif (pos < 0)\n-\t\tmerge_entry(istate, -pos-1, path);\n-}\n-\n-static void merge_all(struct index_state *istate)\n-{\n-\tint i;\n+\tADD_MOF_ARG(orig_blob, orig_mode);\n+\tADD_MOF_ARG(our_blob, our_mode);\n+\tADD_MOF_ARG(their_blob, their_mode);\n \n-\tfor (i = 0; i < istate->cache_nr; i++) {\n-\t\tconst struct cache_entry *ce = istate->cache[i];\n-\t\tif (!ce_stage(ce))\n-\t\t\tcontinue;\n-\t\ti += merge_entry(istate, i, ce->name)-1;\n-\t}\n+\tstrvec_pushv(&cmd.args, arguments);\n+\treturn run_command(&cmd);\n }\n \n int cmd_merge_index(int argc, const char **argv, const char *prefix)\n {\n+\tint err = 0;\n \tint all = 0;\n+\tint one_shot = 0;\n+\tint quiet = 0;\n \tconst char * const usage[] = {\n \t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n \t\tNULL\n@@ -91,6 +64,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tOPT_END(),\n \t};\n #undef OPT__MERGE_INDEX_ALL\n+\tstruct mofs_data data = { 0 };\n \n \t/* Without this we cannot rely on waitpid() to tell\n \t * what happened to our children.\n@@ -109,7 +83,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t/* <merge-program> and its options */\n \tif (!argc)\n \t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n-\tpgm = argv[0];\n+\tdata.program = argv[0];\n \targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n \tif (argc && all)\n \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n@@ -121,12 +95,13 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \tensure_full_index(the_repository->index);\n \n \tif (all)\n-\t\tmerge_all(the_repository->index);\n+\t\terr |= merge_all_index(the_repository->index, one_shot, quiet,\n+\t\t\t\t       merge_one_file, &data);\n \telse\n \t\tfor (size_t i = 0; i < argc; i++)\n-\t\t\tmerge_one_path(the_repository->index, argv[i]);\n+\t\t\terr |= merge_index_path(the_repository->index,\n+\t\t\t\t\t\tone_shot, quiet, argv[i],\n+\t\t\t\t\t\tmerge_one_file, &data);\n \n-\tif (err && !quiet)\n-\t\tdie(_(\"merge program failed\"));\n \treturn err;\n }\ndiff --git a/merge-strategies.c b/merge-strategies.c\nnew file mode 100644\nindex 00000000000..30691fccd77\n--- /dev/null\n+++ b/merge-strategies.c\n@@ -0,0 +1,87 @@\n+#include \"cache.h\"\n+#include \"merge-strategies.h\"\n+\n+static int merge_entry(struct index_state *istate, unsigned int pos,\n+\t\t       const char *path, int *err, merge_index_fn fn,\n+\t\t       void *data)\n+{\n+\tint found = 0;\n+\tconst struct object_id *oids[3] = { 0 };\n+\tunsigned int modes[3] = { 0 };\n+\n+\t*err = 0;\n+\n+\tif (pos >= istate->cache_nr)\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n+\tdo {\n+\t\tconst struct cache_entry *ce = istate->cache[pos];\n+\t\tint stage = ce_stage(ce);\n+\n+\t\tif (strcmp(ce->name, path))\n+\t\t\tbreak;\n+\t\tfound++;\n+\t\toids[stage - 1] = &ce->oid;\n+\t\tmodes[stage - 1] = ce->ce_mode;\n+\t} while (++pos < istate->cache_nr);\n+\tif (!found)\n+\t\tdie(_(\"'%s' is not in the cache\"), path);\n+\n+\tif (fn(istate, oids[0], oids[1], oids[2], path, modes[0], modes[1],\n+\t       modes[2], data))\n+\t\t(*err)++;\n+\n+\treturn found;\n+}\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_index_fn fn, void *data)\n+{\n+\tint err, ret;\n+\tint pos = index_name_pos(istate, path, strlen(path));\n+\n+\t/*\n+\t * If it already exists in the cache as stage0, it's\n+\t * already merged and there is nothing to do.\n+\t */\n+\tif (pos >= 0)\n+\t\treturn 0;\n+\n+\tret = merge_entry(istate, -pos - 1, path, &err, fn, data);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\tif (err) {\n+\t\tif (!quiet && !oneshot)\n+\t\t\tdie(_(\"merge program failed\"));\n+\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_index_fn fn, void *data)\n+{\n+\tint err, ret;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i < istate->cache_nr; i++) {\n+\t\tconst struct cache_entry *ce = istate->cache[i];\n+\t\tif (!ce_stage(ce))\n+\t\t\tcontinue;\n+\n+\t\tret = merge_entry(istate, i, ce->name, &err, fn, data);\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\t\telse if (ret > 0)\n+\t\t\ti += ret - 1;\n+\n+\t\tif (err && !oneshot) {\n+\t\t\tif (!quiet)\n+\t\t\t\tdie(_(\"merge program failed\"));\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\n+\tif (err && !quiet)\n+\t\tdie(_(\"merge program failed\"));\n+\treturn err;\n+}\ndiff --git a/merge-strategies.h b/merge-strategies.h\nnew file mode 100644\nindex 00000000000..cee9168a046\n--- /dev/null\n+++ b/merge-strategies.h\n@@ -0,0 +1,19 @@\n+#ifndef MERGE_STRATEGIES_H\n+#define MERGE_STRATEGIES_H\n+\n+struct object_id;\n+struct index_state;\n+typedef int (*merge_index_fn)(struct index_state *istate,\n+\t\t\t      const struct object_id *orig_blob,\n+\t\t\t      const struct object_id *our_blob,\n+\t\t\t      const struct object_id *their_blob,\n+\t\t\t      const char *path, unsigned int orig_mode,\n+\t\t\t      unsigned int our_mode, unsigned int their_mode,\n+\t\t\t      void *data);\n+\n+int merge_index_path(struct index_state *istate, int oneshot, int quiet,\n+\t\t     const char *path, merge_index_fn fn, void *data);\n+int merge_all_index(struct index_state *istate, int oneshot, int quiet,\n+\t\t    merge_index_fn fn, void *data);\n+\n+#endif /* MERGE_STRATEGIES_H */\n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469079","messageId":"patch-v10-11.12-748fef4434f-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 11/12] merge-index: use \"struct strvec\" and helper to prepare args","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:15Z","receivedAt":"2022-12-15T08:53:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Refactor the code that was libified in the preceding commit to use\nstrvec_pushf() with a helper function, instead of in-place xsnprintf()\ncode that we generate with a macro.\n\nThis is less efficient in term of the number of allocations we do, but\nit's now much clearer what's going on. The logic is simply that we\nhave an argument list like:\n\n\t<merge-program> <oids> <path> <modes>\n\nWhere we always need either an OID/mode pair, or \"\". Now we'll add\nboth to their own strvec, which we then combine at the end.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c | 44 ++++++++++++++++++++++++++-----------------\n 1 file changed, 27 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex 21598a52383..d679272391b 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -7,6 +7,18 @@ struct mofs_data {\n \tconst char *program;\n };\n \n+static void push_arg(struct strvec *oids, struct strvec *modes,\n+\t\t     const struct object_id *oid, const unsigned int mode)\n+{\n+\tif (oid) {\n+\t\tstrvec_push(oids, oid_to_hex(oid));\n+\t\tstrvec_pushf(modes, \"%06o\", mode);\n+\t} else {\n+\t\tstrvec_push(oids, \"\");\n+\t\tstrvec_push(modes, \"\");\n+\t}\n+}\n+\n static int merge_one_file(struct index_state *istate,\n \t\t\t  const struct object_id *orig_blob,\n \t\t\t  const struct object_id *our_blob,\n@@ -15,27 +27,25 @@ static int merge_one_file(struct index_state *istate,\n \t\t\t  unsigned int their_mode, void *data)\n {\n \tstruct mofs_data *d = data;\n-\tconst char *pgm = d->program;\n-\tconst char *arguments[] = { pgm, \"\", \"\", \"\", path, \"\", \"\", \"\", NULL };\n-\tchar hexbuf[4][GIT_MAX_HEXSZ + 1];\n-\tchar ownbuf[4][60];\n-\tint stage = 0;\n+\tconst char *program = d->program;\n+\tstruct strvec oids = STRVEC_INIT;\n+\tstruct strvec modes = STRVEC_INIT;\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n \n-#define ADD_MOF_ARG(oid, mode) \\\n-\tif ((oid)) { \\\n-\t\tstage++; \\\n-\t\toid_to_hex_r(hexbuf[stage], (oid)); \\\n-\t\txsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), \"%06o\", (mode)); \\\n-\t\targuments[stage] = hexbuf[stage]; \\\n-\t\targuments[stage + 4] = ownbuf[stage]; \\\n-\t}\n+\tstrvec_push(&cmd.args, program);\n+\n+\tpush_arg(&oids, &modes, orig_blob, orig_mode);\n+\tpush_arg(&oids, &modes, our_blob, our_mode);\n+\tpush_arg(&oids, &modes, their_blob, their_mode);\n+\n+\tstrvec_pushv(&cmd.args, oids.v);\n+\tstrvec_clear(&oids);\n+\n+\tstrvec_push(&cmd.args, path);\n \n-\tADD_MOF_ARG(orig_blob, orig_mode);\n-\tADD_MOF_ARG(our_blob, our_mode);\n-\tADD_MOF_ARG(their_blob, their_mode);\n+\tstrvec_pushv(&cmd.args, modes.v);\n+\tstrvec_clear(&modes);\n \n-\tstrvec_pushv(&cmd.args, arguments);\n \treturn run_command(&cmd);\n }\n \n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"},{"id":"469080","messageId":"patch-v10-12.12-40b6d296f3a-20221215T084803Z-avarab@gmail.com","threadId":"53755","inReplyTo":"cover-v10-00.12-00000000000-20221215T084803Z-avarab@gmail.com","subject":"[PATCH v10 12/12] merge-index: make the argument parsing sensible & simpler","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-12-15T08:52:16Z","receivedAt":"2022-12-15T08:53:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"In a preceding commit when we migrated to parse_options() we took\npains to be bug-for-bug compatible with the existing command-line\ninterface, if possible.\n\nI.e. we forbade forms like:\n\n\tgit merge-index -a <program>\n\tgit merge-index <program> <opts> -a\n\nBut allowed:\n\n\tgit merge-index <program> -a\n\tgit merge-index <opts> <program> -a\n\nAs the \"-a\" argument was considered be provided for the \"<program>\",\nbut not a part of \"<opts>\".\n\nWe don't really need this strictness, as we don't have two \"-a\"\noptions. It's much simpler to implement a schema where the first\nnon-option argument is the <program>, and the rest are the\n\"<file>...\". We only allow that rest if the \"-a\" option isn't\nsupplied.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/merge-index.c  | 28 ++++++++--------------------\n t/t6060-merge-index.sh | 12 +++++++++---\n 2 files changed, 17 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/merge-index.c b/builtin/merge-index.c\nindex d679272391b..d8b62e4f663 100644\n--- a/builtin/merge-index.c\n+++ b/builtin/merge-index.c\n@@ -59,21 +59,14 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t\tN_(\"git merge-index [-o] [-q] <merge-program> (-a | ([--] <file>...))\"),\n \t\tNULL\n \t};\n-#define OPT__MERGE_INDEX_ALL(v) \\\n-\tOPT_BOOL('a', NULL, (v), \\\n-\t\t N_(\"merge all files in the index that need merging\"))\n \tstruct option options[] = {\n \t\tOPT_BOOL('o', NULL, &one_shot,\n \t\t\t N_(\"don't stop at the first failed merge\")),\n \t\tOPT__QUIET(&quiet, N_(\"be quiet\")),\n-\t\tOPT__MERGE_INDEX_ALL(&all), /* include \"-a\" to show it in \"-bh\" */\n+\t\tOPT_BOOL('a', NULL, &all,\n+\t\t\t N_(\"merge all files in the index that need merging\")),\n \t\tOPT_END(),\n \t};\n-\tstruct option options_prog[] = {\n-\t\tOPT__MERGE_INDEX_ALL(&all),\n-\t\tOPT_END(),\n-\t};\n-#undef OPT__MERGE_INDEX_ALL\n \tstruct mofs_data data = { 0 };\n \n \t/* Without this we cannot rely on waitpid() to tell\n@@ -81,20 +74,15 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix)\n \t */\n \tsignal(SIGCHLD, SIG_DFL);\n \n-\tif (argc < 3)\n-\t\tusage_with_options(usage, options);\n-\n-\t/* Option parsing without <merge-program> options */\n-\targc = parse_options(argc, argv, prefix, options, usage,\n-\t\t\t     PARSE_OPT_STOP_AT_NON_OPTION);\n-\tif (all)\n-\t\tusage_msg_optf(_(\"'%s' option can only be provided after '<merge-program>'\"),\n-\t\t\t      usage, options, \"-a\");\n-\t/* <merge-program> and its options */\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n \tif (!argc)\n \t\tusage_msg_opt(_(\"need a <merge-program> argument\"), usage, options);\n \tdata.program = argv[0];\n-\targc = parse_options(argc, argv, prefix, options_prog, usage, 0);\n+\targv++;\n+\targc--;\n+\tif (!argc && !all)\n+\t\tusage_msg_opt(_(\"need '-a' or '<file>...'\"),\n+\t\t\t      usage, options);\n \tif (argc && all)\n \t\tusage_msg_opt(_(\"'-a' and '<file>...' are mutually exclusive\"),\n \t\t\t      usage, options);\ndiff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh\nindex bc201a69552..4ff9ace7f73 100755\n--- a/t/t6060-merge-index.sh\n+++ b/t/t6060-merge-index.sh\n@@ -22,7 +22,7 @@ test_expect_success 'usage: 2 arguments' '\n \n test_expect_success 'usage: -a before <program>' '\n \tcat >expect <<-\\EOF &&\n-\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n+\tfatal: '\\''-a'\\'' and '\\''<file>...'\\'' are mutually exclusive\n \tEOF\n \ttest_expect_code 129 git merge-index -a b program >out 2>actual.raw &&\n \tgrep \"^fatal:\" actual.raw >actual &&\n@@ -34,7 +34,7 @@ for opt in -q -o\n do\n \ttest_expect_success \"usage: $opt after -a\" '\n \t\tcat >expect <<-EOF &&\n-\t\tfatal: '\\''-a'\\'' option can only be provided after '\\''<merge-program>'\\''\n+\t\tfatal: need a <merge-program> argument\n \t\tEOF\n \t\ttest_expect_code 129 git merge-index -a $opt >out 2>actual.raw &&\n \t\tgrep \"^fatal:\" actual.raw >actual &&\n@@ -43,7 +43,13 @@ do\n \t'\n \n \ttest_expect_success \"usage: $opt program\" '\n-\t\ttest_expect_code 0 git merge-index $opt program\n+\t\tcat >expect <<-EOF &&\n+\t\tfatal: need '\\''-a'\\'' or '\\''<file>...'\\''\n+\t\tEOF\n+\t\ttest_expect_code 129 git merge-index $opt program 2>actual.raw &&\n+\t\tgrep \"^fatal:\" actual.raw >actual &&\n+\t\ttest_must_be_empty out &&\n+\t\ttest_cmp expect actual\n \t'\n done\n \n-- \n2.39.0.rc2.1048.g0e5493b8d5b\n\n"}]}