{"thread":{"id":"54475","subject":"[PATCH 0/4] Beginning of new merge strategy: New API, empty implementation","startedAt":"2020-10-21T13:22:41Z","lastAt":"2020-11-07T06:26:34Z","messageCount":44,"participants":["Elijah Newren via GitGitGadget","Elijah Newren","Taylor Blau","Peter Baumann","Junio C Hamano","Jonathan Tan","Eric Sunshine","SZEDER Gábor","Jacob Keller"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"408051","messageId":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":null,"subject":"[PATCH 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-21T13:22:31Z","receivedAt":"2020-10-21T13:22:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In this series, I try to show the new merge API I have developed in\nmerge-ort and show how it differs from that provided by merge-recursive. I\ndo this in four steps, each corresponding to a patch:\n\n * Provide the new merge-ort.h header file and an empty die-immediately\n   implementation\n * Provide new merge-ort-wrappers.[ch] files, showing how to implement the\n   old API using the new API. The wrappers make it easy to convert existing\n   callsites, too.\n * Provide a demonstration of the flexibility of the new API and its\n   separation of merging steps and updating the index/worktree steps, with a\n   \"fast-rebase\". [Doesn't yet function since merge-ort.c is still an empty\n   die-immediately implementation]\n * Provide the changes to merge, rebase, revert, and cherry-pick so that\n   with an environment variable or config option the new or old backend can\n   be called.\n\nMy next patch series will focus on testsuite changes needed to\nsimultaneously support both merge backends, keyed off an environment\nvariable (thus related to and building on patch 4). Let me know if I'm\nsubmitting series too close together or otherwise need to tweak how I'm\nsending these out...\n\nElijah Newren (4):\n  merge-ort: barebones API of new merge strategy with empty\n    implementation\n  merge-ort-wrappers: new convience wrappers to mimic the old merge API\n  fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n  merge,rebase,revert: select ort or recursive by config or environment\n\n Makefile              |   3 +\n builtin.h             |   1 +\n builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n builtin/merge.c       |  26 +++++-\n builtin/rebase.c      |   9 +-\n builtin/revert.c      |   2 +\n git.c                 |   1 +\n merge-ort-wrappers.c  |  62 +++++++++++++\n merge-ort-wrappers.h  |  25 +++++\n merge-ort.c           |  52 +++++++++++\n merge-ort.h           |  49 ++++++++++\n sequencer.c           |  71 +++++++++++---\n 12 files changed, 496 insertions(+), 15 deletions(-)\n create mode 100644 builtin/fast-rebase.c\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\n\nbase-commit: 69986e19ffcfb9af674ae5180689ab7bbf92ed28\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-895%2Fnewren%2Fort-api-with-empty-implementation-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-895/newren/ort-api-with-empty-implementation-v1\nPull-Request: https://github.com/git/git/pull/895\n-- \ngitgitgadget\n"},{"id":"408052","messageId":"f597609b8872fc2bb4f32c439ab6f74c3e41ce24.1603286556.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","subject":"[PATCH 2/4] merge-ort-wrappers: new convience wrappers to mimic the old merge API","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-21T13:22:33Z","receivedAt":"2020-10-21T13:22:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are a few differences between the new API in merge-ort and the old\nAPI in merge-recursive.  While the new API is more flexible, it might\nfeel like more work at times than the old API.  merge-ort-wrappers\ncreates two convenience wrappers taking the exact same arguments as the\nold merge_trees() and merge_recursive() functions and implements them\nvia the new API.  This makes converting existing callsites easier, and\nserves to highlight some of the differences in the API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile             |  1 +\n merge-ort-wrappers.c | 62 ++++++++++++++++++++++++++++++++++++++++++++\n merge-ort-wrappers.h | 25 ++++++++++++++++++\n 3 files changed, 88 insertions(+)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n\ndiff --git a/Makefile b/Makefile\nindex 088770c2ae..382fe73c76 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -922,6 +922,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n 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.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nnew file mode 100644\nindex 0000000000..e96c1a71be\n--- /dev/null\n+++ b/merge-ort-wrappers.c\n@@ -0,0 +1,62 @@\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n+\n+#include \"commit.h\"\n+\n+static int unclean(struct merge_options *opt, struct tree *head)\n+{\n+\t/* Sanity check on repo state; index must match head */\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (head && repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\tfprintf(stderr, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *merge_base)\n+{\n+\tstruct merge_result result;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tif (oideq(&merge_base->object.oid, &merge->object.oid)) {\n+\t\tprintf(_(\"Already up to date!\"));\n+\t\treturn 1;\n+\t}\n+\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_inmemory_nonrecursive(opt, merge_base, head, merge, &result);\n+\tmerge_switch_to_result(opt, head, &result, 1, 1);\n+\n+\treturn result.clean;\n+}\n+\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *side1,\n+\t\t\tstruct commit *side2,\n+\t\t\tstruct commit_list *merge_bases,\n+\t\t\tstruct commit **result)\n+{\n+\tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\n+\tstruct merge_result tmp;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tmemset(&tmp, 0, sizeof(tmp));\n+\tmerge_inmemory_recursive(opt, merge_bases, side1, side2, &tmp);\n+\tmerge_switch_to_result(opt, head, &tmp, 1, 1);\n+\t*result = NULL;\n+\n+\treturn tmp.clean;\n+}\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nnew file mode 100644\nindex 0000000000..0c4c57adbb\n--- /dev/null\n+++ b/merge-ort-wrappers.h\n@@ -0,0 +1,25 @@\n+#ifndef MERGE_ORT_WRAPPERS_H\n+#define MERGE_ORT_WRAPPERS_H\n+\n+#include \"merge-recursive.h\"\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * Wrapper mimicking the old merge_trees() function.\n+ */\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *common);\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * Wrapper mimicking the old merge_recursive() function.\n+ */\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *h1,\n+\t\t\tstruct commit *h2,\n+\t\t\tstruct commit_list *ancestors,\n+\t\t\tstruct commit **result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408053","messageId":"54ef18182cf3a4b5d02889c139e495fa68588652.1603286555.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","subject":"[PATCH 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-21T13:22:32Z","receivedAt":"2020-10-21T13:22:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis is the beginning of a new merge strategy.  While there are some API\ndifferences, and the implementation has some differences in behavior, it\nis essentially meant as an eventual drop-in replacement for\nmerge-recursive.c.  However, it is being built to exist side-by-side\nwith merge-recursive so that we have plenty of time to find out how\nthose differences pan out in the real world while people can still fall\nback to merge-recursive.  (Also, I intend to avoid modifying\nmerge-recursive during this process, to keep it stable.)\n\nThe primary difference noticable here is that the updating of the\nworking tree and index is not done simultaneously with the merge\nalgorithm, but is a separate post-processing step.  The new API is\ndesigned so that one can do repeated merges (e.g. during a rebase or\ncherry-pick) and only update the index and working tree one time at the\nend instead of updating it with every intermediate result.  Also, one\ncan perform a merge between two branches, neither of which match the\nindex or the working tree, without clobbering the index or working tree.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile    |  1 +\n merge-ort.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n merge-ort.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 102 insertions(+)\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..088770c2ae 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n+LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-recursive.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort.c b/merge-ort.c\nnew file mode 100644\nindex 0000000000..5230364a8d\n--- /dev/null\n+++ b/merge-ort.c\n@@ -0,0 +1,52 @@\n+/*\n+ * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n+ * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n+ * to replace\n+ *\n+ *   git merge [-s recursive]\n+ *\n+ * with\n+ *\n+ *   git merge -s ort\n+ *\n+ * Note: git's parser allows the space between '-s' and its argument to be\n+ * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n+ * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n+ */\n+\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs)\n+{\n+\tdie(\"Not yet implemented\");\n+\tmerge_finalize(opt, result);\n+}\n+\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_inmemory_nonrecursive(struct merge_options *opt,\n+\t\t\t\t struct tree *merge_base,\n+\t\t\t\t struct tree *side1,\n+\t\t\t\t struct tree *side2,\n+\t\t\t\t struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_inmemory_recursive(struct merge_options *opt,\n+\t\t\t      struct commit_list *merge_bases,\n+\t\t\t      struct commit *side1,\n+\t\t\t      struct commit *side2,\n+\t\t\t      struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\ndiff --git a/merge-ort.h b/merge-ort.h\nnew file mode 100644\nindex 0000000000..9c655cd3ad\n--- /dev/null\n+++ b/merge-ort.h\n@@ -0,0 +1,49 @@\n+#ifndef MERGE_ORT_H\n+#define MERGE_ORT_H\n+\n+#include \"merge-recursive.h\"\n+\n+struct commit;\n+struct tree;\n+\n+struct merge_result {\n+\t/* whether the merge is clean */\n+\tint clean;\n+\n+\t/* Result of merge.  If !clean, represents what would go in worktree */\n+\tstruct tree *tree;\n+\n+\t/*\n+\t * Additional metadata used by merge_switch_to_result() or future calls\n+\t * to merge_inmemory_*().\n+\t */\n+\tunsigned _;\n+\tvoid *priv;\n+};\n+\n+/* rename-detecting three-way merge, no recursion. */\n+void merge_inmemory_recursive(struct merge_options *opt,\n+\t\t\t      struct commit_list *merge_bases,\n+\t\t\t      struct commit *side1,\n+\t\t\t      struct commit *side2,\n+\t\t\t      struct merge_result *result);\n+\n+/* rename-detecting three-way merge with recursive ancestor consolidation. */\n+void merge_inmemory_nonrecursive(struct merge_options *opt,\n+\t\t\t\t struct tree *merge_base,\n+\t\t\t\t struct tree *side1,\n+\t\t\t\t struct tree *side2,\n+\t\t\t\t struct merge_result *result);\n+\n+/* Update the working tree and index from head to result after inmemory merge */\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs);\n+\n+/* Do needed cleanup when not calling merge_switch_to_result() */\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408054","messageId":"06ecea215fc8f03fba38acdf6a66b358de3adba5.1603286556.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","subject":"[PATCH 4/4] merge,rebase,revert: select ort or recursive by config or environment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-21T13:22:35Z","receivedAt":"2020-10-21T13:22:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAllow the testsuite to run where it treats requests for \"recursive\" or\nthe default merge algorithm via consulting the environment variable\nGIT_TEST_MERGE_ALGORITHM which is expected to either be \"recursive\" (the\nold traditional algorithm) or \"ort\" (the new algorithm).\n\nAlso, allow folks to pick the new algorithm via config setting.  It\nturns out builtin/merge.c already had a way to allow users to specify a\ndifferent default merge algorithm: pull.twohead.  Rather odd\nconfiguration name (especially to be in the 'pull' namespace rather than\n'merge') but it's there.  Add that same configuration to rebase,\ncherry-pick, and revert.\n\nThis required updating the various callsites that called merge_trees()\nor merge_recursive() to conditionally call the new API, so this serves\nas another demonstration of what the new API looks and feels like.\nThere are almost certainly some callsites that have not yet been\nmodified to work with the new merge algorithm, but this represents the\nones that I have been testing with thus far.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge.c  | 26 ++++++++++++++++--\n builtin/rebase.c |  9 +++++-\n builtin/revert.c |  2 ++\n sequencer.c      | 71 ++++++++++++++++++++++++++++++++++++++++--------\n 4 files changed, 93 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..87dfc9bc06 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -28,6 +28,7 @@\n #include \"rerere.h\"\n #include \"help.h\"\n #include \"merge-recursive.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n #include \"fmt-merge-msg.h\"\n@@ -88,6 +89,7 @@ static int no_verify;\n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n \t{ \"octopus\",    DEFAULT_OCTOPUS },\n+\t{ \"ort\",        NO_TRIVIAL },\n \t{ \"resolve\",    0 },\n \t{ \"ours\",       NO_FAST_FORWARD | NO_TRIVIAL },\n \t{ \"subtree\",    NO_FAST_FORWARD | NO_TRIVIAL },\n@@ -159,10 +161,17 @@ static struct strategy *get_strategy(const char *name)\n \tstruct strategy *ret;\n \tstatic struct cmdnames main_cmds, other_cmds;\n \tstatic int loaded;\n+\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n \tif (!name)\n \t\treturn NULL;\n \n+\tif (default_strategy &&\n+\t    !strcmp(default_strategy, \"ort\") &&\n+\t    !strcmp(name, \"recursive\")) {\n+\t\tname = \"ort\";\n+\t}\n+\n \tfor (i = 0; i < ARRAY_SIZE(all_strategy); i++)\n \t\tif (!strcmp(name, all_strategy[i].name))\n \t\t\treturn &all_strategy[i];\n@@ -701,7 +710,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)\n \t\treturn error(_(\"Unable to write index.\"));\n \n-\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\")) {\n+\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n+\t    !strcmp(strategy, \"ort\")) {\n \t\tstruct lock_file lock = LOCK_INIT;\n \t\tint clean, x;\n \t\tstruct commit *result;\n@@ -732,8 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\tcommit_list_insert(j->item, &reversed);\n \n \t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n-\t\tclean = merge_recursive(&o, head,\n-\t\t\t\tremoteheads->item, reversed, &result);\n+\t\tif (!strcmp(strategy, \"ort\"))\n+\t\t\tclean = merge_ort_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\t    reversed, &result);\n+\t\telse\n+\t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\treversed, &result);\n \t\tif (clean < 0)\n \t\t\texit(128);\n \t\tif (write_locked_index(&the_index, &lock,\n@@ -1264,6 +1278,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (branch)\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n \n+\tif (!pull_twohead) {\n+\t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n+\t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n+\t\t\tpull_twohead = \"ort\";\n+\t}\n+\n \tinit_diff_ui_defaults();\n \tgit_config(git_merge_config, NULL);\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex eeca53382f..9719aa25da 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -119,6 +119,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \treplay.action = REPLAY_INTERACTIVE_REBASE;\n+\treplay.strategy = NULL;\n \tsequencer_init_config(&replay);\n \n \treplay.signoff = opts->signoff;\n@@ -136,7 +137,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n-\treplay.strategy = opts->strategy;\n+\tif (opts->strategy)\n+\t\treplay.strategy = opts->strategy;\n \n \tif (opts->strategy_opts)\n \t\tparse_strategy_opts(&replay, opts->strategy_opts);\n@@ -1771,6 +1773,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t    options.default_backend);\n \t}\n \n+\tif (options.type == REBASE_MERGE &&\n+\t    !options.strategy &&\n+\t    getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\toptions.strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_PRESERVE_MERGES:\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex f61cc5d82c..c7cb0c1a18 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -202,6 +202,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t/* These option values will be free()d */\n \topts->gpg_sign = xstrdup_or_null(opts->gpg_sign);\n \topts->strategy = xstrdup_or_null(opts->strategy);\n+\tif (!opts->strategy && getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\topts->strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n \n \tif (cmd == 'q') {\n \t\tint ret = sequencer_remove_state(opts);\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..163475ae31 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -14,7 +14,8 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n-#include \"merge-recursive.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"refs.h\"\n #include \"strvec.h\"\n #include \"quote.h\"\n@@ -204,6 +205,20 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (!opts->strategy && !strcmp(k, \"pull.twohead\")) {\n+\t\tint ret = git_config_string((const char**)&opts->strategy, k, v);\n+\t\tif (ret == 0) {\n+\t\t\t/*\n+\t\t\t * pull.twohead is allowed to be multi-valued; we only\n+\t\t\t * care about the first value.\n+\t\t\t */\n+\t\t\tchar *tmp = strchr(opts->strategy, ' ');\n+\t\t\tif (tmp)\n+\t\t\t\t*tmp = '\\0';\n+\t\t}\n+\t\treturn ret;\n+\t}\n+\n \tstatus = git_gpg_config(k, v, NULL);\n \tif (status)\n \t\treturn status;\n@@ -595,8 +610,9 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n+\tstruct merge_result result;\n \tstruct tree *next_tree, *base_tree, *head_tree;\n-\tint clean;\n+\tint clean, show_output;\n \tint i;\n \tstruct lock_file index_lock = LOCK_INIT;\n \n@@ -620,12 +636,27 @@ static int do_recursive_merge(struct repository *r,\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tparse_merge_opt(&o, opts->xopts[i]);\n \n-\tclean = merge_trees(&o,\n-\t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree);\n-\tif (is_rebase_i(opts) && clean <= 0)\n-\t\tfputs(o.obuf.buf, stdout);\n-\tstrbuf_release(&o.obuf);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\tmemset(&result, 0, sizeof(result));\n+\t\tmerge_inmemory_nonrecursive(&o, base_tree, head_tree, next_tree,\n+\t\t\t\t\t    &result);\n+\t\tshow_output = !is_rebase_i(opts) || !result.clean;\n+\t\t/*\n+\t\t * TODO: merge_switch_to_result will update index/working tree;\n+\t\t * we only really want to do that if !result.clean || this is\n+\t\t * the final patch to be picked.  But determining this is the\n+\t\t * final patch would take some work, and \"head_tree\" would need\n+\t\t * to be replace with the tree the index matched before we\n+\t\t * started doing any picks.\n+\t\t */\n+\t\tmerge_switch_to_result(&o, head_tree, &result, 1, show_output);\n+\t\tclean = result.clean;\n+\t} else {\n+\t\tclean = merge_trees(&o, head_tree, next_tree, base_tree);\n+\t\tif (is_rebase_i(opts) && clean <= 0)\n+\t\t\tfputs(o.obuf.buf, stdout);\n+\t\tstrbuf_release(&o.obuf);\n+\t}\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n@@ -1991,7 +2022,10 @@ static int do_pick_commit(struct repository *r,\n \n \tif (is_rebase_i(opts) && write_author_script(msg.message) < 0)\n \t\tres = -1;\n-\telse if (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || command == TODO_REVERT) {\n+\telse if (!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\") ||\n+\t\t command == TODO_REVERT) {\n \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n \t\t\t\t\t &head, &msgbuf, opts);\n \t\tif (res < 0)\n@@ -3485,7 +3519,9 @@ static int do_merge(struct repository *r,\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts_nr &&\n-\t\t(!opts->strategy || !strcmp(opts->strategy, \"recursive\")) ?\n+\t\t(!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\")) ?\n \t\tNULL : opts->strategy;\n \tstruct merge_options o;\n \tint merge_arg_len, oneline_offset, can_fast_forward, ret, k;\n@@ -3722,7 +3758,20 @@ static int do_merge(struct repository *r,\n \to.branch2 = ref_name.buf;\n \to.buffer_output = 2;\n \n-\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\t/*\n+\t\t * TODO: Should use merge_inmemory_recursive() and\n+\t\t * merge_switch_to_result(), skipping the call to\n+\t\t * merge_switch_to_result() when we don't actually need to\n+\t\t * update the index and working copy immediately.\n+\t\t */\n+\t\tret = merge_ort_recursive(&o,\n+\t\t\t\t\t  head_commit, merge_commit, reversed,\n+\t\t\t\t\t  &i);\n+\t} else {\n+\t\tret = merge_recursive(&o, head_commit, merge_commit, reversed,\n+\t\t\t\t      &i);\n+\t}\n \tif (ret <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \ngitgitgadget\n"},{"id":"408055","messageId":"a1357fb3b309984db000cc8fa212db1d24f7ab4c.1603286556.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","subject":"[PATCH 3/4] fast-rebase: demonstrate merge-ort's API via temporary/hidden command","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-21T13:22:34Z","receivedAt":"2020-10-21T13:22:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd a special built-in that is only of use to git-developers and only\nduring the development of merge-ort, and which is designed to\nimmediately fail and print:\n   git: 'fast-rebase' is not a git command\nunless a special GIT_TEST_MERGE_ALGORITHM environment variable is set.\n\nThis special builtin serves two purposes:\n\n  1) Demonstrate the desired API of merge-ort.  In particular,\n     fast-rebase takes advantage of the separation of the merging\n     operation from the updating of the index and working tree, to\n     allow it to pick N commits, but only update the index and working\n     tree once at the end.  Look for the calls to\n     merge_inmemory_nonrecursive() and merge_switch_to_result().\n\n  2) Provide a convenient benchmark that isn't polluted by the heavy\n     disk writing and forking of unnecessary processes that comes from\n     sequencer.c and merge-recursive.c.  fast-rebase is not meant to\n     replace sequencer.c, just give ideas on how sequencer.c can be\n     changed.  Updating sequencer.c with these goals is probably a\n     large amount of work; writing a simple targeted command with\n     no documentation, less-than-useful help messages, numerous\n     limitations in terms of flags it can accept and situations it can\n     handle, and which is flagged off from users is a much easier\n     interim step.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile              |   1 +\n builtin.h             |   1 +\n builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n git.c                 |   1 +\n 4 files changed, 213 insertions(+)\n create mode 100644 builtin/fast-rebase.c\n\ndiff --git a/Makefile b/Makefile\nindex 382fe73c76..1b40d780fa 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1087,6 +1087,7 @@ BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/env--helper.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fast-import.o\n+BUILTIN_OBJS += builtin/fast-rebase.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\n BUILTIN_OBJS += builtin/fmt-merge-msg.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..75ff7dc8a9 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -151,6 +151,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix);\n int cmd_env__helper(int argc, const char **argv, const char *prefix);\n int cmd_fast_export(int argc, const char **argv, const char *prefix);\n int cmd_fast_import(int argc, const char **argv, const char *prefix);\n+int cmd_fast_rebase(int argc, const char **argv, const char *prefix);\n int cmd_fetch(int argc, const char **argv, const char *prefix);\n int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/fast-rebase.c b/builtin/fast-rebase.c\nnew file mode 100644\nindex 0000000000..ae78a8b1c1\n--- /dev/null\n+++ b/builtin/fast-rebase.c\n@@ -0,0 +1,210 @@\n+/*\n+ * \"git fast-rebase\" builtin command\n+ *\n+ * FAST: Forking Any Subprocesses (is) Taboo\n+ *\n+ * This is meant SOLELY as a demo of what is possible.  sequencer.c and\n+ * rebase.c should be refactored to use the ideas here, rather than attempting\n+ * to extend this file to replace those (unless Phillip or Dscho say that\n+ * refactoring is too hard and we need a clean slate, but I'm guessing that\n+ * refactoring is the better route).\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"builtin.h\"\n+\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"lockfile.h\"\n+#include \"merge-ort.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"sequencer.h\"\n+#include \"strvec.h\"\n+#include \"tree.h\"\n+\n+static const char *short_commit_name(struct commit *commit)\n+{\n+\treturn find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);\n+}\n+\n+static struct commit *peel_committish(const char *name)\n+{\n+\tstruct object *obj;\n+\tstruct object_id oid;\n+\n+\tif (get_oid(name, &oid))\n+\t\treturn NULL;\n+\tobj = parse_object(the_repository, &oid);\n+\treturn (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);\n+}\n+\n+static char *get_author(const char *message)\n+{\n+\tsize_t len;\n+\tconst char *a;\n+\n+\ta = find_commit_header(message, \"author\", &len);\n+\tif (a)\n+\t\treturn xmemdupz(a, len);\n+\n+\treturn NULL;\n+}\n+\n+static struct commit *create_commit(struct tree *tree,\n+\t\t\t\t    struct commit *based_on,\n+\t\t\t\t    struct commit *parent)\n+{\n+\tstruct object_id ret;\n+\tstruct object *obj;\n+\tstruct commit_list *parents = NULL;\n+\tchar *author;\n+\tchar *sign_commit = NULL;\n+\tstruct commit_extra_header *extra;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *out_enc = get_commit_output_encoding();\n+\tconst char *message = logmsg_reencode(based_on, NULL, out_enc);\n+\tconst char *orig_message = NULL;\n+\tconst char *exclude_gpgsig[] = { \"gpgsig\", NULL };\n+\n+\tcommit_list_insert(parent, &parents);\n+\textra = read_commit_extra_headers(based_on, exclude_gpgsig);\n+\tfind_commit_subject(message, &orig_message);\n+\tstrbuf_addstr(&msg, orig_message);\n+\tauthor = get_author(message);\n+\treset_ident_date();\n+\tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n+\t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n+\t\terror(_(\"failed to write commit object\"));\n+\t\treturn NULL;\n+\t}\n+\tfree(author);\n+\tstrbuf_release(&msg);\n+\n+\tobj = parse_object(the_repository, &ret);\n+\treturn (struct commit *)obj;\n+}\n+\n+int cmd_fast_rebase(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct commit *onto;\n+\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n+\tstruct object_id head;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint clean = 1;\n+\tstruct strvec rev_walk_args = STRVEC_INIT;\n+\tstruct rev_info revs;\n+\tstruct commit *commit;\n+\tstruct merge_options merge_opt;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n+\tstruct merge_result result;\n+\tstruct strbuf reflog_msg = STRBUF_INIT;\n+\tstruct strbuf branch_name = STRBUF_INIT;\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n+\t\tprintf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n+\t\texit(129);\n+\t}\n+\n+\tif (!getenv(\"GIT_TEST_MERGE_ALGORITHM\")) {\n+\t\tfprintf_ln(stderr, _(\"git: 'fast-rebase' is not a git command. See 'git --help'.\"));\n+\t\texit(1);\n+\t}\n+\n+\tif (argc != 5 || strcmp(argv[1], \"--onto\"))\n+\t\tdie(\"usage: read the code, figure out how to use it, then do so\");\n+\n+\tonto = peel_committish(argv[2]);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\n+\t/* Sanity check */\n+\tif (get_oid(\"HEAD\", &head))\n+\t\tdie(_(\"Cannot read HEAD\"));\n+\tassert(oideq(&onto->object.oid, &head));\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\tassert(repo_read_index(the_repository) >= 0);\n+\n+\trepo_init_revisions(the_repository, &revs, NULL);\n+\trevs.verbose_header = 1;\n+\trevs.max_parents = 1;\n+\trevs.cherry_mark = 1;\n+\trevs.limited = 1;\n+\trevs.reverse = 1;\n+\trevs.right_only = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[4], \"--not\", argv[3], NULL);\n+\n+\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1)\n+\t\treturn error(_(\"unhandled options\"));\n+\n+\tstrvec_clear(&rev_walk_args);\n+\n+\tif (prepare_revision_walk(&revs) < 0)\n+\t\treturn error(_(\"error preparing revisions\"));\n+\n+\tinit_merge_options(&merge_opt, the_repository);\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_opt.show_rename_progress = 1;\n+\tmerge_opt.branch1 = \"HEAD\";\n+\thead_tree = get_commit_tree(onto);\n+\tresult.tree = head_tree;\n+\tlast_commit = onto;\n+\twhile ((commit = get_revision(&revs))) {\n+\t\tstruct commit *base;\n+\n+\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n+\t\t\toid_to_hex(&commit->object.oid));\n+\t\tassert(commit->parents && !commit->parents->next);\n+\t\tbase = commit->parents->item;\n+\n+\t\tnext_tree = get_commit_tree(commit);\n+\t\tbase_tree = get_commit_tree(base);\n+\n+\t\tmerge_opt.branch2 = short_commit_name(commit);\n+\t\tmerge_opt.ancestor = xstrfmt(\"parent of %s\", merge_opt.branch2);\n+\n+\t\tmerge_inmemory_nonrecursive(&merge_opt,\n+\t\t\t\t\t    base_tree,\n+\t\t\t\t\t    result.tree,\n+\t\t\t\t\t    next_tree,\n+\t\t\t\t\t    &result);\n+\n+\t\tfree((char*)merge_opt.ancestor);\n+\t\tmerge_opt.ancestor = NULL;\n+\t\tif (!result.clean)\n+\t\t\tdie(\"Aborting: Hit a conflict and restarting is not implemented.\");\n+\t\tlast_picked_commit = commit;\n+\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n+\t}\n+\tfprintf(stderr, \"\\nDone.\\n\");\n+\t/* TODO: There should be some kind of rev_info_free(&revs) call... */\n+\tmemset(&revs, 0, sizeof(revs));\n+\n+\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\n+\tif (result.clean < 0)\n+\t\texit(128);\n+\n+\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n+\t\t    oid_to_hex(&last_picked_commit->object.oid),\n+\t\t    oid_to_hex(&last_commit->object.oid));\n+\tif (update_ref(reflog_msg.buf, branch_name.buf,\n+\t\t       &last_commit->object.oid,\n+\t\t       &last_picked_commit->object.oid,\n+\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n+\t\terror(_(\"could not update %s\"), argv[4]);\n+\t\tdie(\"Failed to update %s\", argv[4]);\n+\t}\n+\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n+\t\tdie(_(\"unable to update HEAD\"));\n+\tstrbuf_release(&reflog_msg);\n+\tstrbuf_release(&branch_name);\n+\n+\tprime_cache_tree(the_repository, the_repository->index, result.tree);\n+\tif (write_locked_index(&the_index, &lock,\n+\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n+\t\tdie(_(\"unable to write %s\"), get_index_file());\n+\treturn (clean == 0);\n+}\ndiff --git a/git.c b/git.c\nindex 4bdcdad2cc..af84f11e69 100644\n--- a/git.c\n+++ b/git.c\n@@ -512,6 +512,7 @@ static struct cmd_struct commands[] = {\n \t{ \"env--helper\", cmd_env__helper },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fast-import\", cmd_fast_import, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"fast-rebase\", cmd_fast_rebase, RUN_SETUP /* | NEED_WORK_TREE */ },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP | NO_PARSEOPT },\n \t{ \"fmt-merge-msg\", cmd_fmt_merge_msg, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"408129","messageId":"CABPp-BEGo8qfaoz6vUs+ktvmNUZdiHjwda468Qkh5uaD-wAd4w@mail.gmail.com","threadId":"54475","inReplyTo":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-22T00:16:05Z","receivedAt":"2020-10-22T00:16:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Wed, Oct 21, 2020 at 6:22 AM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> In this series, I try to show the new merge API I have developed in\n> merge-ort and show how it differs from that provided by merge-recursive. I\n> do this in four steps, each corresponding to a patch:\n\nI should probably call out that even if folks don't have time to\nreview patches, I'm particularly interested in opinions on the\nfollowing two questions:\n  * Are the \"pull.twohead\" and \"GIT_TEST_MERGE_ALGORITHM\" names in\npatch 4 good/bad/ugly?  (especially the mapping from \"pull\" to revert,\ncherry-pick, rebase, and merge?)\n  * Is it too weird to have a temporary/hidden builtin, in patch 3?\nIf so, what is a good alternative?\n\nThanks,\nElijah\n"},{"id":"408268","messageId":"X5MdD5Ft/DNbCfQv@nand.local","threadId":"54475","inReplyTo":"54ef18182cf3a4b5d02889c139e495fa68588652.1603286555.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2020-10-23T18:12:31Z","receivedAt":"2020-10-23T18:12:39Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 21, 2020 at 01:22:32PM +0000, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n>\n> This is the beginning of a new merge strategy.  While there are some API\n> differences, and the implementation has some differences in behavior, it\n> is essentially meant as an eventual drop-in replacement for\n> merge-recursive.c.  However, it is being built to exist side-by-side\n> with merge-recursive so that we have plenty of time to find out how\n> those differences pan out in the real world while people can still fall\n> back to merge-recursive.  (Also, I intend to avoid modifying\n> merge-recursive during this process, to keep it stable.)\n>\n> The primary difference noticable here is that the updating of the\n> working tree and index is not done simultaneously with the merge\n> algorithm, but is a separate post-processing step.  The new API is\n> designed so that one can do repeated merges (e.g. during a rebase or\n> cherry-pick) and only update the index and working tree one time at the\n> end instead of updating it with every intermediate result.  Also, one\n> can perform a merge between two branches, neither of which match the\n> index or the working tree, without clobbering the index or working tree.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  Makefile    |  1 +\n>  merge-ort.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  merge-ort.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 102 insertions(+)\n>  create mode 100644 merge-ort.c\n>  create mode 100644 merge-ort.h\n>\n> diff --git a/Makefile b/Makefile\n> index 95571ee3fc..088770c2ae 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n>  LIB_OBJS += match-trees.o\n>  LIB_OBJS += mem-pool.o\n>  LIB_OBJS += merge-blobs.o\n> +LIB_OBJS += merge-ort.o\n>  LIB_OBJS += merge-recursive.o\n>  LIB_OBJS += merge.o\n>  LIB_OBJS += mergesort.o\n> diff --git a/merge-ort.c b/merge-ort.c\n> new file mode 100644\n> index 0000000000..5230364a8d\n> --- /dev/null\n> +++ b/merge-ort.c\n> @@ -0,0 +1,52 @@\n> +/*\n> + * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n> + * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n> + * to replace\n> + *\n> + *   git merge [-s recursive]\n> + *\n> + * with\n> + *\n> + *   git merge -s ort\n> + *\n> + * Note: git's parser allows the space between '-s' and its argument to be\n> + * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n> + * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n> + */\n\nOne thing that might be good here (and I realize that you sort of get to\nit in your next patch) is some example usage. Maybe it's good enough to\nsay \"see the $FILE_MODIFIED_BY_NEXT_PATCH for example usage\", but it may\nbe good to be a little bit clearer here.\n\nPerhaps it may even be appropriate to add a\nDocumentation/technical/api-merge-ort.txt or something (if you haven't\nalready in one of your other branches...).\n\n> +\n> +#include \"cache.h\"\n> +#include \"merge-ort.h\"\n> +\n> +void merge_switch_to_result(struct merge_options *opt,\n> +\t\t\t    struct tree *head,\n> +\t\t\t    struct merge_result *result,\n> +\t\t\t    int update_worktree_and_index,\n> +\t\t\t    int display_update_msgs)\n> +{\n> +\tdie(\"Not yet implemented\");\n> +\tmerge_finalize(opt, result);\n> +}\n> +\n> +void merge_finalize(struct merge_options *opt,\n> +\t\t    struct merge_result *result)\n> +{\n> +\tdie(\"Not yet implemented\");\n> +}\n> +\n> +void merge_inmemory_nonrecursive(struct merge_options *opt,\n> +\t\t\t\t struct tree *merge_base,\n> +\t\t\t\t struct tree *side1,\n> +\t\t\t\t struct tree *side2,\n> +\t\t\t\t struct merge_result *result)\n> +{\n> +\tdie(\"Not yet implemented\");\n> +}\n> +\n> +void merge_inmemory_recursive(struct merge_options *opt,\n> +\t\t\t      struct commit_list *merge_bases,\n> +\t\t\t      struct commit *side1,\n> +\t\t\t      struct commit *side2,\n> +\t\t\t      struct merge_result *result)\n> +{\n> +\tdie(\"Not yet implemented\");\n> +}\n> diff --git a/merge-ort.h b/merge-ort.h\n> new file mode 100644\n> index 0000000000..9c655cd3ad\n> --- /dev/null\n> +++ b/merge-ort.h\n> @@ -0,0 +1,49 @@\n> +#ifndef MERGE_ORT_H\n> +#define MERGE_ORT_H\n> +\n> +#include \"merge-recursive.h\"\n> +\n> +struct commit;\n> +struct tree;\n> +\n> +struct merge_result {\n> +\t/* whether the merge is clean */\n> +\tint clean;\n> +\n> +\t/* Result of merge.  If !clean, represents what would go in worktree */\n> +\tstruct tree *tree;\n> +\n> +\t/*\n> +\t * Additional metadata used by merge_switch_to_result() or future calls\n> +\t * to merge_inmemory_*().\n> +\t */\n> +\tunsigned _;\n\nI was a little surprised that '_' is allowed, since I can't think of any\nother variable that is called that (and a search with \"git grep ' _;' --\n**/*.h\" confirms that this is the only one. It does compile with\nDEVELOPER=1, so...\n\nIs this meant to be a flags-like? I believe you that it's necessary, but\nI wonder if it could be named more clearly, even though it's private.\n\n> +\tvoid *priv;\n> +};\n> +\n> +/* rename-detecting three-way merge, no recursion. */\n> +void merge_inmemory_recursive(struct merge_options *opt,\n> +\t\t\t      struct commit_list *merge_bases,\n> +\t\t\t      struct commit *side1,\n> +\t\t\t      struct commit *side2,\n> +\t\t\t      struct merge_result *result);\n> +\n> +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n> +void merge_inmemory_nonrecursive(struct merge_options *opt,\n> +\t\t\t\t struct tree *merge_base,\n> +\t\t\t\t struct tree *side1,\n> +\t\t\t\t struct tree *side2,\n> +\t\t\t\t struct merge_result *result);\n\nGood. This API looks sane to me, and contains nothing more nor less than\nI'd expect. I appreciate that the _recursive declaration is separate\nfrom the _nonrecursive one (and not hidden behind a flag that switches\nbetween the two).\n\n> +/* Update the working tree and index from head to result after inmemory merge */\n> +void merge_switch_to_result(struct merge_options *opt,\n> +\t\t\t    struct tree *head,\n> +\t\t\t    struct merge_result *result,\n> +\t\t\t    int update_worktree_and_index,\n> +\t\t\t    int display_update_msgs);\n\nSeems like these last two could probably be made into an enum and passed\nas flags instead of adding more parameters on the end.\n\n> +/* Do needed cleanup when not calling merge_switch_to_result() */\n> +void merge_finalize(struct merge_options *opt,\n> +\t\t    struct merge_result *result);\n> +\n> +#endif\n> --\n> gitgitgadget\n>\nThanks,\nTaylor\n"},{"id":"408273","messageId":"CABPp-BFWzE=4pD6+NZknGTNgvav5yR7hTXx4ZN19EWCKTckK6g@mail.gmail.com","threadId":"54475","inReplyTo":"X5MdD5Ft/DNbCfQv@nand.local","subject":"Re: [PATCH 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-23T19:02:52Z","receivedAt":"2020-10-23T19:03:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 23, 2020 at 11:12 AM Taylor Blau <me@ttaylorr.com> wrote:\n>\n> On Wed, Oct 21, 2020 at 01:22:32PM +0000, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > This is the beginning of a new merge strategy.  While there are some API\n> > differences, and the implementation has some differences in behavior, it\n> > is essentially meant as an eventual drop-in replacement for\n> > merge-recursive.c.  However, it is being built to exist side-by-side\n> > with merge-recursive so that we have plenty of time to find out how\n> > those differences pan out in the real world while people can still fall\n> > back to merge-recursive.  (Also, I intend to avoid modifying\n> > merge-recursive during this process, to keep it stable.)\n> >\n> > The primary difference noticable here is that the updating of the\n> > working tree and index is not done simultaneously with the merge\n> > algorithm, but is a separate post-processing step.  The new API is\n> > designed so that one can do repeated merges (e.g. during a rebase or\n> > cherry-pick) and only update the index and working tree one time at the\n> > end instead of updating it with every intermediate result.  Also, one\n> > can perform a merge between two branches, neither of which match the\n> > index or the working tree, without clobbering the index or working tree.\n> >\n> > Signed-off-by: Elijah Newren <newren@gmail.com>\n> > ---\n> >  Makefile    |  1 +\n> >  merge-ort.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n> >  merge-ort.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n> >  3 files changed, 102 insertions(+)\n> >  create mode 100644 merge-ort.c\n> >  create mode 100644 merge-ort.h\n> >\n> > diff --git a/Makefile b/Makefile\n> > index 95571ee3fc..088770c2ae 100644\n> > --- a/Makefile\n> > +++ b/Makefile\n> > @@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n> >  LIB_OBJS += match-trees.o\n> >  LIB_OBJS += mem-pool.o\n> >  LIB_OBJS += merge-blobs.o\n> > +LIB_OBJS += merge-ort.o\n> >  LIB_OBJS += merge-recursive.o\n> >  LIB_OBJS += merge.o\n> >  LIB_OBJS += mergesort.o\n> > diff --git a/merge-ort.c b/merge-ort.c\n> > new file mode 100644\n> > index 0000000000..5230364a8d\n> > --- /dev/null\n> > +++ b/merge-ort.c\n> > @@ -0,0 +1,52 @@\n> > +/*\n> > + * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n> > + * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n> > + * to replace\n> > + *\n> > + *   git merge [-s recursive]\n> > + *\n> > + * with\n> > + *\n> > + *   git merge -s ort\n> > + *\n> > + * Note: git's parser allows the space between '-s' and its argument to be\n> > + * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n> > + * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n> > + */\n>\n> One thing that might be good here (and I realize that you sort of get to\n> it in your next patch) is some example usage. Maybe it's good enough to\n\nThe remainder of this series, all 3 patches, are solely about this.  I\nthink smashing all four patches into one would just make it too large,\nand obscure the different types of usage.\n\n> say \"see the $FILE_MODIFIED_BY_NEXT_PATCH for example usage\", but it may\n> be good to be a little bit clearer here.\n\nI could do that, however.  (Though it'd be THE_NEXT_THREE_PATCHES,\nrather than just FILE_MODIFIED_BY_NEXT_PATCH)\n\n> Perhaps it may even be appropriate to add a\n> Documentation/technical/api-merge-ort.txt or something (if you haven't\n> already in one of your other branches...).\n\nI don't have such a thing.  Since there are basically just two\nfunctions that callers would be calling (one of either the recursive\nor nonrecursive variants, plus one of either merge_finalize() or\nmerge_switch_to_result()), it didn't occur to me to write anything\nlike that up.  I had thought about documenting the implementation\nstrategy and performance strategies and whatnot, but haven't yet done\nthat either (other than my talk at Git Merge 2020, of course).  If the\nAPI usage is confusing, I can add that, though if calling two\nfunctions is confusing, there might be something wrong with the two\nfunctions.\n\n> > +\n> > +#include \"cache.h\"\n> > +#include \"merge-ort.h\"\n> > +\n> > +void merge_switch_to_result(struct merge_options *opt,\n> > +                         struct tree *head,\n> > +                         struct merge_result *result,\n> > +                         int update_worktree_and_index,\n> > +                         int display_update_msgs)\n> > +{\n> > +     die(\"Not yet implemented\");\n> > +     merge_finalize(opt, result);\n> > +}\n> > +\n> > +void merge_finalize(struct merge_options *opt,\n> > +                 struct merge_result *result)\n> > +{\n> > +     die(\"Not yet implemented\");\n> > +}\n> > +\n> > +void merge_inmemory_nonrecursive(struct merge_options *opt,\n> > +                              struct tree *merge_base,\n> > +                              struct tree *side1,\n> > +                              struct tree *side2,\n> > +                              struct merge_result *result)\n> > +{\n> > +     die(\"Not yet implemented\");\n> > +}\n> > +\n> > +void merge_inmemory_recursive(struct merge_options *opt,\n> > +                           struct commit_list *merge_bases,\n> > +                           struct commit *side1,\n> > +                           struct commit *side2,\n> > +                           struct merge_result *result)\n> > +{\n> > +     die(\"Not yet implemented\");\n> > +}\n> > diff --git a/merge-ort.h b/merge-ort.h\n> > new file mode 100644\n> > index 0000000000..9c655cd3ad\n> > --- /dev/null\n> > +++ b/merge-ort.h\n> > @@ -0,0 +1,49 @@\n> > +#ifndef MERGE_ORT_H\n> > +#define MERGE_ORT_H\n> > +\n> > +#include \"merge-recursive.h\"\n> > +\n> > +struct commit;\n> > +struct tree;\n> > +\n> > +struct merge_result {\n> > +     /* whether the merge is clean */\n> > +     int clean;\n> > +\n> > +     /* Result of merge.  If !clean, represents what would go in worktree */\n> > +     struct tree *tree;\n> > +\n> > +     /*\n> > +      * Additional metadata used by merge_switch_to_result() or future calls\n> > +      * to merge_inmemory_*().\n> > +      */\n> > +     unsigned _;\n>\n> I was a little surprised that '_' is allowed, since I can't think of any\n> other variable that is called that (and a search with \"git grep ' _;' --\n> **/*.h\" confirms that this is the only one. It does compile with\n> DEVELOPER=1, so...\n>\n> Is this meant to be a flags-like? I believe you that it's necessary, but\n> I wonder if it could be named more clearly, even though it's private.\n\nYeah, this one is a little weird.  It technically could be dropped,\nbut is there solely to help catch API mis-use; most of the API is\nrelatively obvious but there's one gotcha...\n\nThe merge_inmemory_*recursive() functions accept both a struct\nmerge_options and a struct merge_result.  merge_result is _mostly_ an\noutput so people might omit zeroing it out beforehand, but the\nmerge_result can be re-used between multiple calls when cherry-picking\nor rebasing a sequence of commits.  When it is re-used in such a\nmanner, it enables some performance optimizations (information\ncomputed in a picking a previous commit need not be recomputed).\nHowever, that raises a sticky question: how can you tell the\ndifference between result->priv being reused, vs. being nonzero just\nbecause it's uninitialized garbage?  If it's the latter, looking into\nresult->priv might give you a segfault or at least nonsensical results\nthat take a while to debug.  The simple way I avoided this problem for\ncallers is that I expect result->_ to either be 0 or have a special\nvalue, and if it doesn't have one of those two values, then I BUG().\nAt the end of a merge, I set result->_ to that special value.  The\nspecial value is not exported and is only known within merge-ort.c.\n\nYou could get away without this _ member and just let valgrind or\nsegfaults catch the accidental use of uninitialized values, but since\nresult looks and feels like an output-only variable it was something\nthat was easy to misuse and I wanted an extra helping guardrail to\nmake that kind of debugging loop be shorter.\n\nI guess if I were to rename it, I would call it 'initialized' or\n\"carry_over\" or something.  It's really just a boolean variable rather\nthan a set of flags, but an actual boolean bit that is uninitialized\nprovides ~0% confidence in knowing whether the struct really is\ninitialized or not, while an unsigned has enough bits of safety that\nan uninitialized value is cosmically unlikely to match my special\n'RESULT_INITIALIZED' value.\n\nWith this field, the one gotcha is basically trivial to debug; without\nit, it takes a bit more effort.  If I were to remove this extra field,\nthen it'd make more sense to write up more documentation about API\nusage.\n\n>\n> > +     void *priv;\n> > +};\n> > +\n> > +/* rename-detecting three-way merge, no recursion. */\n> > +void merge_inmemory_recursive(struct merge_options *opt,\n> > +                           struct commit_list *merge_bases,\n> > +                           struct commit *side1,\n> > +                           struct commit *side2,\n> > +                           struct merge_result *result);\n> > +\n> > +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n> > +void merge_inmemory_nonrecursive(struct merge_options *opt,\n> > +                              struct tree *merge_base,\n> > +                              struct tree *side1,\n> > +                              struct tree *side2,\n> > +                              struct merge_result *result);\n>\n> Good. This API looks sane to me, and contains nothing more nor less than\n> I'd expect. I appreciate that the _recursive declaration is separate\n> from the _nonrecursive one (and not hidden behind a flag that switches\n> between the two).\n>\n> > +/* Update the working tree and index from head to result after inmemory merge */\n> > +void merge_switch_to_result(struct merge_options *opt,\n> > +                         struct tree *head,\n> > +                         struct merge_result *result,\n> > +                         int update_worktree_and_index,\n> > +                         int display_update_msgs);\n>\n> Seems like these last two could probably be made into an enum and passed\n> as flags instead of adding more parameters on the end.\n\nYes, it'd make the API slightly less self-documenting, but would be\nmore flexible for adding additional flags in the future.  I had\nthought about that earlier, but I really just don't see what other\nflags could possibly be added.  Maybe when I resume working on\nevil-(merge/revert/cherry-pick) detection I'll have a better feel for\nthat, but I had just leaned towards leaving it as these two flags for\nnow.\n\n> > +/* Do needed cleanup when not calling merge_switch_to_result() */\n> > +void merge_finalize(struct merge_options *opt,\n> > +                 struct merge_result *result);\n> > +\n> > +#endif\n> > --\n> > gitgitgadget\n"},{"id":"408289","messageId":"CAJm9OHcEGfkgEsTZhD4qKG9Ns5fu=COToWz0XkHhPuJ_DtGzgA@mail.gmail.com","threadId":"54475","inReplyTo":"54ef18182cf3a4b5d02889c139e495fa68588652.1603286555.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Peter Baumann","fromEmail":"peter.baumann@gmail.com","sentAt":"2020-10-24T10:46:12Z","receivedAt":"2020-10-24T10:47:27Z","isPatch":true,"sender":{"key":"peter.baumann@gmail.com","avatar":null},"body":"Am Do., 22. Okt. 2020 um 03:28 Uhr schrieb Elijah Newren via\nGitGitGadget <gitgitgadget@gmail.com>:\n[... snipping a lot of unneeded context ...]\n> diff --git a/merge-ort.h b/merge-ort.h\n> new file mode 100644\n> index 0000000000..9c655cd3ad\n> --- /dev/null\n> +++ b/merge-ort.h\n> @@ -0,0 +1,49 @@\n> +#ifndef MERGE_ORT_H\n> +#define MERGE_ORT_H\n> +\n> +#include \"merge-recursive.h\"\n> +\n> +struct commit;\n> +struct tree;\n> +\n> +struct merge_result {\n> +       /* whether the merge is clean */\n> +       int clean;\n> +\n> +       /* Result of merge.  If !clean, represents what would go in worktree */\n> +       struct tree *tree;\n> +\n> +       /*\n> +        * Additional metadata used by merge_switch_to_result() or future calls\n> +        * to merge_inmemory_*().\n> +        */\n> +       unsigned _;\n> +       void *priv;\n> +};\n> +\n> +/* rename-detecting three-way merge, no recursion. */\n                                                                   ^^^^^^^^^^^^^\n> +void merge_inmemory_recursive(struct merge_options *opt,\n                                           ^^^^^^^^^\n> +                             struct commit_list *merge_bases,\n> +                             struct commit *side1,\n> +                             struct commit *side2,\n> +                             struct merge_result *result);\n> +\n> +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n                                                                  ^^^^^^^^^^^^^\n> +void merge_inmemory_nonrecursive(struct merge_options *opt,\n                                            ^^^^^^^^^^^^\n> +                                struct tree *merge_base,\n> +                                struct tree *side1,\n> +                                struct tree *side2,\n> +                                struct merge_result *result);\n> +\n\nThe comments don't fit to the actually method name. I assume they\nshould be switched?\n\n-Peter\n"},{"id":"408293","messageId":"CABPp-BGnZEDXxvTR9GxW+Y2FzG9DCtOcoUPTipoAFjdhh8cpJg@mail.gmail.com","threadId":"54475","inReplyTo":"CAJm9OHdfxh8SGdteD48eDCA=ihGZmKJD-E67PFhCdFR63RSSTA@mail.gmail.com","subject":"Re: [PATCH 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-24T14:54:49Z","receivedAt":"2020-10-24T14:55:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Sat, Oct 24, 2020 at 3:57 AM Peter Baumann <peter.baumann@gmail.com> wrote:\n>\n> Am Sa., 24. Okt. 2020 um 11:58 Uhr schrieb Peter Baumann <peter.baumann@gmail.com>:\n>>>\n>>> Am Do., 22. Okt. 2020 um 03:28 Uhr schrieb Elijah Newren via GitGitGadget <gitgitgadget@gmail.com>:\n>>> [... ]\n>>>>\n>>>> diff --git a/merge-ort.h b/merge-ort.h\n>>>> new file mode 100644\n>>>> index 0000000000..9c655cd3ad\n>>>> --- /dev/null\n>>>> +++ b/merge-ort.h\n>>>> @@ -0,0 +1,49 @@\n>>>> +#ifndef MERGE_ORT_H\n>>>> +#define MERGE_ORT_H\n>>>> +\n>>>> +#include \"merge-recursive.h\"\n>>>> +\n>>>> +struct commit;\n>>>> +struct tree;\n>>>> +\n>>>> +struct merge_result {\n>>>> +       /* whether the merge is clean */\n>>>> +       int clean;\n>>>> +\n>>>> +       /* Result of merge.  If !clean, represents what would go in worktree */\n>>>> +       struct tree *tree;\n>>>> +\n>>>> +       /*\n>>>> +        * Additional metadata used by merge_switch_to_result() or future calls\n>>>> +        * to merge_inmemory_*().\n>>>> +        */\n>>>> +       unsigned _;\n>>>> +       void *priv;\n>>>> +};\n>>>> +\n>>>> +/* rename-detecting three-way merge, no recursion. */\n>>>\n>>>                                                                     ^^^^^^^^^^^^\n>>>>\n>>>> +void merge_inmemory_recursive(struct merge_options *opt,\n>>>\n>>>                                             ^^^^^^^^\n>>>>\n>>>> +                             struct commit_list *merge_bases,\n>>>> +                             struct commit *side1,\n>>>> +                             struct commit *side2,\n>>>> +                             struct merge_result *result);\n>>>> +\n>>>> +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n>>>\n>>>                                                                    ^^^^^^^^^^^^^^^^^^^^^^\n>>>>\n>>>> +void merge_inmemory_nonrecursive(struct merge_options *opt,\n>>>\n>>>                                             ^^^^^^^^^^^^^\n>>>>\n>>>> +                                struct tree *merge_base,\n>>>> +                                struct tree *side1,\n>>>> +                                struct tree *side2,\n>>>> +                                struct merge_result *result);\n>>>> +\n>>>> [...]\n>>>\n>>>\n>>> Looks like the comments above don't match the method names they are describing.\n>>> I assume they should be just switched?\n\nWhoops, indeed.  Will fix; thanks for spotting.\n\n>>>\n>>> -Peter\n>>>\n> Sorry, I completely messed up the markers. Serves me well for not using a fixed with font.\n> Here it is again, highlighting only the relevant section:\n>\n> > +/* rename-detecting three-way merge, no recursion. */\n>                                         ^^^^^^^^^^^^^\n> > +void merge_inmemory_recursive(struct merge_options *opt,\n>                        ^^^^^^^^^\n>\n> [...]\n> > +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n>                                  ^^^^^^^^^^^^^^^^^^^^^^\n> > +void merge_inmemory_nonrecursive(struct merge_options *opt,\n>                        ^^^^^^^^^^^^\n>\n>\n"},{"id":"408408","messageId":"a9fff811a2157f26908eb87f5b6e17dcf8c8e50d.1603731448.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v2.git.git.1603731448.gitgitgadget@gmail.com","subject":"[PATCH v2 2/4] merge-ort-wrappers: new convience wrappers to mimic the old merge API","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-26T16:57:26Z","receivedAt":"2020-10-26T16:57:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are a few differences between the new API in merge-ort and the old\nAPI in merge-recursive.  While the new API is more flexible, it might\nfeel like more work at times than the old API.  merge-ort-wrappers\ncreates two convenience wrappers taking the exact same arguments as the\nold merge_trees() and merge_recursive() functions and implements them\nvia the new API.  This makes converting existing callsites easier, and\nserves to highlight some of the differences in the API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile             |  1 +\n merge-ort-wrappers.c | 62 ++++++++++++++++++++++++++++++++++++++++++++\n merge-ort-wrappers.h | 25 ++++++++++++++++++\n 3 files changed, 88 insertions(+)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n\ndiff --git a/Makefile b/Makefile\nindex 088770c2ae..382fe73c76 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -922,6 +922,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n 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.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nnew file mode 100644\nindex 0000000000..e96c1a71be\n--- /dev/null\n+++ b/merge-ort-wrappers.c\n@@ -0,0 +1,62 @@\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n+\n+#include \"commit.h\"\n+\n+static int unclean(struct merge_options *opt, struct tree *head)\n+{\n+\t/* Sanity check on repo state; index must match head */\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (head && repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\tfprintf(stderr, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *merge_base)\n+{\n+\tstruct merge_result result;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tif (oideq(&merge_base->object.oid, &merge->object.oid)) {\n+\t\tprintf(_(\"Already up to date!\"));\n+\t\treturn 1;\n+\t}\n+\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_inmemory_nonrecursive(opt, merge_base, head, merge, &result);\n+\tmerge_switch_to_result(opt, head, &result, 1, 1);\n+\n+\treturn result.clean;\n+}\n+\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *side1,\n+\t\t\tstruct commit *side2,\n+\t\t\tstruct commit_list *merge_bases,\n+\t\t\tstruct commit **result)\n+{\n+\tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\n+\tstruct merge_result tmp;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tmemset(&tmp, 0, sizeof(tmp));\n+\tmerge_inmemory_recursive(opt, merge_bases, side1, side2, &tmp);\n+\tmerge_switch_to_result(opt, head, &tmp, 1, 1);\n+\t*result = NULL;\n+\n+\treturn tmp.clean;\n+}\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nnew file mode 100644\nindex 0000000000..0c4c57adbb\n--- /dev/null\n+++ b/merge-ort-wrappers.h\n@@ -0,0 +1,25 @@\n+#ifndef MERGE_ORT_WRAPPERS_H\n+#define MERGE_ORT_WRAPPERS_H\n+\n+#include \"merge-recursive.h\"\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * Wrapper mimicking the old merge_trees() function.\n+ */\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *common);\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * Wrapper mimicking the old merge_recursive() function.\n+ */\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *h1,\n+\t\t\tstruct commit *h2,\n+\t\t\tstruct commit_list *ancestors,\n+\t\t\tstruct commit **result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408409","messageId":"5f6c97b889503ad52cd4a409f0a4027df9c92ae9.1603731448.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v2.git.git.1603731448.gitgitgadget@gmail.com","subject":"[PATCH v2 4/4] merge,rebase,revert: select ort or recursive by config or environment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-26T16:57:28Z","receivedAt":"2020-10-26T16:57:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAllow the testsuite to run where it treats requests for \"recursive\" or\nthe default merge algorithm via consulting the environment variable\nGIT_TEST_MERGE_ALGORITHM which is expected to either be \"recursive\" (the\nold traditional algorithm) or \"ort\" (the new algorithm).\n\nAlso, allow folks to pick the new algorithm via config setting.  It\nturns out builtin/merge.c already had a way to allow users to specify a\ndifferent default merge algorithm: pull.twohead.  Rather odd\nconfiguration name (especially to be in the 'pull' namespace rather than\n'merge') but it's there.  Add that same configuration to rebase,\ncherry-pick, and revert.\n\nThis required updating the various callsites that called merge_trees()\nor merge_recursive() to conditionally call the new API, so this serves\nas another demonstration of what the new API looks and feels like.\nThere are almost certainly some callsites that have not yet been\nmodified to work with the new merge algorithm, but this represents the\nones that I have been testing with thus far.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge.c  | 26 ++++++++++++++++--\n builtin/rebase.c |  9 +++++-\n builtin/revert.c |  2 ++\n sequencer.c      | 71 ++++++++++++++++++++++++++++++++++++++++--------\n 4 files changed, 93 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..87dfc9bc06 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -28,6 +28,7 @@\n #include \"rerere.h\"\n #include \"help.h\"\n #include \"merge-recursive.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n #include \"fmt-merge-msg.h\"\n@@ -88,6 +89,7 @@ static int no_verify;\n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n \t{ \"octopus\",    DEFAULT_OCTOPUS },\n+\t{ \"ort\",        NO_TRIVIAL },\n \t{ \"resolve\",    0 },\n \t{ \"ours\",       NO_FAST_FORWARD | NO_TRIVIAL },\n \t{ \"subtree\",    NO_FAST_FORWARD | NO_TRIVIAL },\n@@ -159,10 +161,17 @@ static struct strategy *get_strategy(const char *name)\n \tstruct strategy *ret;\n \tstatic struct cmdnames main_cmds, other_cmds;\n \tstatic int loaded;\n+\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n \tif (!name)\n \t\treturn NULL;\n \n+\tif (default_strategy &&\n+\t    !strcmp(default_strategy, \"ort\") &&\n+\t    !strcmp(name, \"recursive\")) {\n+\t\tname = \"ort\";\n+\t}\n+\n \tfor (i = 0; i < ARRAY_SIZE(all_strategy); i++)\n \t\tif (!strcmp(name, all_strategy[i].name))\n \t\t\treturn &all_strategy[i];\n@@ -701,7 +710,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)\n \t\treturn error(_(\"Unable to write index.\"));\n \n-\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\")) {\n+\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n+\t    !strcmp(strategy, \"ort\")) {\n \t\tstruct lock_file lock = LOCK_INIT;\n \t\tint clean, x;\n \t\tstruct commit *result;\n@@ -732,8 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\tcommit_list_insert(j->item, &reversed);\n \n \t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n-\t\tclean = merge_recursive(&o, head,\n-\t\t\t\tremoteheads->item, reversed, &result);\n+\t\tif (!strcmp(strategy, \"ort\"))\n+\t\t\tclean = merge_ort_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\t    reversed, &result);\n+\t\telse\n+\t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\treversed, &result);\n \t\tif (clean < 0)\n \t\t\texit(128);\n \t\tif (write_locked_index(&the_index, &lock,\n@@ -1264,6 +1278,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (branch)\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n \n+\tif (!pull_twohead) {\n+\t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n+\t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n+\t\t\tpull_twohead = \"ort\";\n+\t}\n+\n \tinit_diff_ui_defaults();\n \tgit_config(git_merge_config, NULL);\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex eeca53382f..9719aa25da 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -119,6 +119,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \treplay.action = REPLAY_INTERACTIVE_REBASE;\n+\treplay.strategy = NULL;\n \tsequencer_init_config(&replay);\n \n \treplay.signoff = opts->signoff;\n@@ -136,7 +137,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n-\treplay.strategy = opts->strategy;\n+\tif (opts->strategy)\n+\t\treplay.strategy = opts->strategy;\n \n \tif (opts->strategy_opts)\n \t\tparse_strategy_opts(&replay, opts->strategy_opts);\n@@ -1771,6 +1773,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t    options.default_backend);\n \t}\n \n+\tif (options.type == REBASE_MERGE &&\n+\t    !options.strategy &&\n+\t    getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\toptions.strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_PRESERVE_MERGES:\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex f61cc5d82c..c7cb0c1a18 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -202,6 +202,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t/* These option values will be free()d */\n \topts->gpg_sign = xstrdup_or_null(opts->gpg_sign);\n \topts->strategy = xstrdup_or_null(opts->strategy);\n+\tif (!opts->strategy && getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\topts->strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n \n \tif (cmd == 'q') {\n \t\tint ret = sequencer_remove_state(opts);\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..163475ae31 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -14,7 +14,8 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n-#include \"merge-recursive.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"refs.h\"\n #include \"strvec.h\"\n #include \"quote.h\"\n@@ -204,6 +205,20 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (!opts->strategy && !strcmp(k, \"pull.twohead\")) {\n+\t\tint ret = git_config_string((const char**)&opts->strategy, k, v);\n+\t\tif (ret == 0) {\n+\t\t\t/*\n+\t\t\t * pull.twohead is allowed to be multi-valued; we only\n+\t\t\t * care about the first value.\n+\t\t\t */\n+\t\t\tchar *tmp = strchr(opts->strategy, ' ');\n+\t\t\tif (tmp)\n+\t\t\t\t*tmp = '\\0';\n+\t\t}\n+\t\treturn ret;\n+\t}\n+\n \tstatus = git_gpg_config(k, v, NULL);\n \tif (status)\n \t\treturn status;\n@@ -595,8 +610,9 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n+\tstruct merge_result result;\n \tstruct tree *next_tree, *base_tree, *head_tree;\n-\tint clean;\n+\tint clean, show_output;\n \tint i;\n \tstruct lock_file index_lock = LOCK_INIT;\n \n@@ -620,12 +636,27 @@ static int do_recursive_merge(struct repository *r,\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tparse_merge_opt(&o, opts->xopts[i]);\n \n-\tclean = merge_trees(&o,\n-\t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree);\n-\tif (is_rebase_i(opts) && clean <= 0)\n-\t\tfputs(o.obuf.buf, stdout);\n-\tstrbuf_release(&o.obuf);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\tmemset(&result, 0, sizeof(result));\n+\t\tmerge_inmemory_nonrecursive(&o, base_tree, head_tree, next_tree,\n+\t\t\t\t\t    &result);\n+\t\tshow_output = !is_rebase_i(opts) || !result.clean;\n+\t\t/*\n+\t\t * TODO: merge_switch_to_result will update index/working tree;\n+\t\t * we only really want to do that if !result.clean || this is\n+\t\t * the final patch to be picked.  But determining this is the\n+\t\t * final patch would take some work, and \"head_tree\" would need\n+\t\t * to be replace with the tree the index matched before we\n+\t\t * started doing any picks.\n+\t\t */\n+\t\tmerge_switch_to_result(&o, head_tree, &result, 1, show_output);\n+\t\tclean = result.clean;\n+\t} else {\n+\t\tclean = merge_trees(&o, head_tree, next_tree, base_tree);\n+\t\tif (is_rebase_i(opts) && clean <= 0)\n+\t\t\tfputs(o.obuf.buf, stdout);\n+\t\tstrbuf_release(&o.obuf);\n+\t}\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n@@ -1991,7 +2022,10 @@ static int do_pick_commit(struct repository *r,\n \n \tif (is_rebase_i(opts) && write_author_script(msg.message) < 0)\n \t\tres = -1;\n-\telse if (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || command == TODO_REVERT) {\n+\telse if (!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\") ||\n+\t\t command == TODO_REVERT) {\n \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n \t\t\t\t\t &head, &msgbuf, opts);\n \t\tif (res < 0)\n@@ -3485,7 +3519,9 @@ static int do_merge(struct repository *r,\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts_nr &&\n-\t\t(!opts->strategy || !strcmp(opts->strategy, \"recursive\")) ?\n+\t\t(!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\")) ?\n \t\tNULL : opts->strategy;\n \tstruct merge_options o;\n \tint merge_arg_len, oneline_offset, can_fast_forward, ret, k;\n@@ -3722,7 +3758,20 @@ static int do_merge(struct repository *r,\n \to.branch2 = ref_name.buf;\n \to.buffer_output = 2;\n \n-\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\t/*\n+\t\t * TODO: Should use merge_inmemory_recursive() and\n+\t\t * merge_switch_to_result(), skipping the call to\n+\t\t * merge_switch_to_result() when we don't actually need to\n+\t\t * update the index and working copy immediately.\n+\t\t */\n+\t\tret = merge_ort_recursive(&o,\n+\t\t\t\t\t  head_commit, merge_commit, reversed,\n+\t\t\t\t\t  &i);\n+\t} else {\n+\t\tret = merge_recursive(&o, head_commit, merge_commit, reversed,\n+\t\t\t\t      &i);\n+\t}\n \tif (ret <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \ngitgitgadget\n"},{"id":"408410","messageId":"f38d140c153bc9b94d02c17c70a92dcd889a8ba5.1603731448.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v2.git.git.1603731448.gitgitgadget@gmail.com","subject":"[PATCH v2 3/4] fast-rebase: demonstrate merge-ort's API via temporary/hidden command","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-26T16:57:27Z","receivedAt":"2020-10-26T16:57:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd a special built-in that is only of use to git-developers and only\nduring the development of merge-ort, and which is designed to\nimmediately fail and print:\n   git: 'fast-rebase' is not a git command\nunless a special GIT_TEST_MERGE_ALGORITHM environment variable is set.\n\nThis special builtin serves two purposes:\n\n  1) Demonstrate the desired API of merge-ort.  In particular,\n     fast-rebase takes advantage of the separation of the merging\n     operation from the updating of the index and working tree, to\n     allow it to pick N commits, but only update the index and working\n     tree once at the end.  Look for the calls to\n     merge_inmemory_nonrecursive() and merge_switch_to_result().\n\n  2) Provide a convenient benchmark that isn't polluted by the heavy\n     disk writing and forking of unnecessary processes that comes from\n     sequencer.c and merge-recursive.c.  fast-rebase is not meant to\n     replace sequencer.c, just give ideas on how sequencer.c can be\n     changed.  Updating sequencer.c with these goals is probably a\n     large amount of work; writing a simple targeted command with\n     no documentation, less-than-useful help messages, numerous\n     limitations in terms of flags it can accept and situations it can\n     handle, and which is flagged off from users is a much easier\n     interim step.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile              |   1 +\n builtin.h             |   1 +\n builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n git.c                 |   1 +\n 4 files changed, 213 insertions(+)\n create mode 100644 builtin/fast-rebase.c\n\ndiff --git a/Makefile b/Makefile\nindex 382fe73c76..1b40d780fa 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1087,6 +1087,7 @@ BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/env--helper.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fast-import.o\n+BUILTIN_OBJS += builtin/fast-rebase.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\n BUILTIN_OBJS += builtin/fmt-merge-msg.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..75ff7dc8a9 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -151,6 +151,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix);\n int cmd_env__helper(int argc, const char **argv, const char *prefix);\n int cmd_fast_export(int argc, const char **argv, const char *prefix);\n int cmd_fast_import(int argc, const char **argv, const char *prefix);\n+int cmd_fast_rebase(int argc, const char **argv, const char *prefix);\n int cmd_fetch(int argc, const char **argv, const char *prefix);\n int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/fast-rebase.c b/builtin/fast-rebase.c\nnew file mode 100644\nindex 0000000000..ae78a8b1c1\n--- /dev/null\n+++ b/builtin/fast-rebase.c\n@@ -0,0 +1,210 @@\n+/*\n+ * \"git fast-rebase\" builtin command\n+ *\n+ * FAST: Forking Any Subprocesses (is) Taboo\n+ *\n+ * This is meant SOLELY as a demo of what is possible.  sequencer.c and\n+ * rebase.c should be refactored to use the ideas here, rather than attempting\n+ * to extend this file to replace those (unless Phillip or Dscho say that\n+ * refactoring is too hard and we need a clean slate, but I'm guessing that\n+ * refactoring is the better route).\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"builtin.h\"\n+\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"lockfile.h\"\n+#include \"merge-ort.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"sequencer.h\"\n+#include \"strvec.h\"\n+#include \"tree.h\"\n+\n+static const char *short_commit_name(struct commit *commit)\n+{\n+\treturn find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);\n+}\n+\n+static struct commit *peel_committish(const char *name)\n+{\n+\tstruct object *obj;\n+\tstruct object_id oid;\n+\n+\tif (get_oid(name, &oid))\n+\t\treturn NULL;\n+\tobj = parse_object(the_repository, &oid);\n+\treturn (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);\n+}\n+\n+static char *get_author(const char *message)\n+{\n+\tsize_t len;\n+\tconst char *a;\n+\n+\ta = find_commit_header(message, \"author\", &len);\n+\tif (a)\n+\t\treturn xmemdupz(a, len);\n+\n+\treturn NULL;\n+}\n+\n+static struct commit *create_commit(struct tree *tree,\n+\t\t\t\t    struct commit *based_on,\n+\t\t\t\t    struct commit *parent)\n+{\n+\tstruct object_id ret;\n+\tstruct object *obj;\n+\tstruct commit_list *parents = NULL;\n+\tchar *author;\n+\tchar *sign_commit = NULL;\n+\tstruct commit_extra_header *extra;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *out_enc = get_commit_output_encoding();\n+\tconst char *message = logmsg_reencode(based_on, NULL, out_enc);\n+\tconst char *orig_message = NULL;\n+\tconst char *exclude_gpgsig[] = { \"gpgsig\", NULL };\n+\n+\tcommit_list_insert(parent, &parents);\n+\textra = read_commit_extra_headers(based_on, exclude_gpgsig);\n+\tfind_commit_subject(message, &orig_message);\n+\tstrbuf_addstr(&msg, orig_message);\n+\tauthor = get_author(message);\n+\treset_ident_date();\n+\tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n+\t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n+\t\terror(_(\"failed to write commit object\"));\n+\t\treturn NULL;\n+\t}\n+\tfree(author);\n+\tstrbuf_release(&msg);\n+\n+\tobj = parse_object(the_repository, &ret);\n+\treturn (struct commit *)obj;\n+}\n+\n+int cmd_fast_rebase(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct commit *onto;\n+\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n+\tstruct object_id head;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint clean = 1;\n+\tstruct strvec rev_walk_args = STRVEC_INIT;\n+\tstruct rev_info revs;\n+\tstruct commit *commit;\n+\tstruct merge_options merge_opt;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n+\tstruct merge_result result;\n+\tstruct strbuf reflog_msg = STRBUF_INIT;\n+\tstruct strbuf branch_name = STRBUF_INIT;\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n+\t\tprintf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n+\t\texit(129);\n+\t}\n+\n+\tif (!getenv(\"GIT_TEST_MERGE_ALGORITHM\")) {\n+\t\tfprintf_ln(stderr, _(\"git: 'fast-rebase' is not a git command. See 'git --help'.\"));\n+\t\texit(1);\n+\t}\n+\n+\tif (argc != 5 || strcmp(argv[1], \"--onto\"))\n+\t\tdie(\"usage: read the code, figure out how to use it, then do so\");\n+\n+\tonto = peel_committish(argv[2]);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\n+\t/* Sanity check */\n+\tif (get_oid(\"HEAD\", &head))\n+\t\tdie(_(\"Cannot read HEAD\"));\n+\tassert(oideq(&onto->object.oid, &head));\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\tassert(repo_read_index(the_repository) >= 0);\n+\n+\trepo_init_revisions(the_repository, &revs, NULL);\n+\trevs.verbose_header = 1;\n+\trevs.max_parents = 1;\n+\trevs.cherry_mark = 1;\n+\trevs.limited = 1;\n+\trevs.reverse = 1;\n+\trevs.right_only = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[4], \"--not\", argv[3], NULL);\n+\n+\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1)\n+\t\treturn error(_(\"unhandled options\"));\n+\n+\tstrvec_clear(&rev_walk_args);\n+\n+\tif (prepare_revision_walk(&revs) < 0)\n+\t\treturn error(_(\"error preparing revisions\"));\n+\n+\tinit_merge_options(&merge_opt, the_repository);\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_opt.show_rename_progress = 1;\n+\tmerge_opt.branch1 = \"HEAD\";\n+\thead_tree = get_commit_tree(onto);\n+\tresult.tree = head_tree;\n+\tlast_commit = onto;\n+\twhile ((commit = get_revision(&revs))) {\n+\t\tstruct commit *base;\n+\n+\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n+\t\t\toid_to_hex(&commit->object.oid));\n+\t\tassert(commit->parents && !commit->parents->next);\n+\t\tbase = commit->parents->item;\n+\n+\t\tnext_tree = get_commit_tree(commit);\n+\t\tbase_tree = get_commit_tree(base);\n+\n+\t\tmerge_opt.branch2 = short_commit_name(commit);\n+\t\tmerge_opt.ancestor = xstrfmt(\"parent of %s\", merge_opt.branch2);\n+\n+\t\tmerge_inmemory_nonrecursive(&merge_opt,\n+\t\t\t\t\t    base_tree,\n+\t\t\t\t\t    result.tree,\n+\t\t\t\t\t    next_tree,\n+\t\t\t\t\t    &result);\n+\n+\t\tfree((char*)merge_opt.ancestor);\n+\t\tmerge_opt.ancestor = NULL;\n+\t\tif (!result.clean)\n+\t\t\tdie(\"Aborting: Hit a conflict and restarting is not implemented.\");\n+\t\tlast_picked_commit = commit;\n+\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n+\t}\n+\tfprintf(stderr, \"\\nDone.\\n\");\n+\t/* TODO: There should be some kind of rev_info_free(&revs) call... */\n+\tmemset(&revs, 0, sizeof(revs));\n+\n+\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\n+\tif (result.clean < 0)\n+\t\texit(128);\n+\n+\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n+\t\t    oid_to_hex(&last_picked_commit->object.oid),\n+\t\t    oid_to_hex(&last_commit->object.oid));\n+\tif (update_ref(reflog_msg.buf, branch_name.buf,\n+\t\t       &last_commit->object.oid,\n+\t\t       &last_picked_commit->object.oid,\n+\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n+\t\terror(_(\"could not update %s\"), argv[4]);\n+\t\tdie(\"Failed to update %s\", argv[4]);\n+\t}\n+\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n+\t\tdie(_(\"unable to update HEAD\"));\n+\tstrbuf_release(&reflog_msg);\n+\tstrbuf_release(&branch_name);\n+\n+\tprime_cache_tree(the_repository, the_repository->index, result.tree);\n+\tif (write_locked_index(&the_index, &lock,\n+\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n+\t\tdie(_(\"unable to write %s\"), get_index_file());\n+\treturn (clean == 0);\n+}\ndiff --git a/git.c b/git.c\nindex 4bdcdad2cc..af84f11e69 100644\n--- a/git.c\n+++ b/git.c\n@@ -512,6 +512,7 @@ static struct cmd_struct commands[] = {\n \t{ \"env--helper\", cmd_env__helper },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fast-import\", cmd_fast_import, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"fast-rebase\", cmd_fast_rebase, RUN_SETUP /* | NEED_WORK_TREE */ },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP | NO_PARSEOPT },\n \t{ \"fmt-merge-msg\", cmd_fmt_merge_msg, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"408411","messageId":"pull.895.v2.git.git.1603731448.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.git.git.1603286555.gitgitgadget@gmail.com","subject":"[PATCH v2 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-26T16:57:24Z","receivedAt":"2020-10-26T16:57:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In this series, I try to show the new merge API I have developed in\nmerge-ort and show how it differs from that provided by merge-recursive. I\ndo this in four steps, each corresponding to a patch.\n\nChanges since v1 (thanks to Taylor and Peter for the suggestions in their\nreviews):\n\n * Point out in the first commit message that the next three patches will be\n   providing example usage of the new API.\n * Rename a variable away from '_' in a way that continues the \"private\"\n   theme.\n * Unswap function comments that were placed next to the wrong functions.\n\nElijah Newren (4):\n  merge-ort: barebones API of new merge strategy with empty\n    implementation\n  merge-ort-wrappers: new convience wrappers to mimic the old merge API\n  fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n  merge,rebase,revert: select ort or recursive by config or environment\n\n Makefile              |   3 +\n builtin.h             |   1 +\n builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n builtin/merge.c       |  26 +++++-\n builtin/rebase.c      |   9 +-\n builtin/revert.c      |   2 +\n git.c                 |   1 +\n merge-ort-wrappers.c  |  62 +++++++++++++\n merge-ort-wrappers.h  |  25 +++++\n merge-ort.c           |  52 +++++++++++\n merge-ort.h           |  49 ++++++++++\n sequencer.c           |  71 +++++++++++---\n 12 files changed, 496 insertions(+), 15 deletions(-)\n create mode 100644 builtin/fast-rebase.c\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\n\nbase-commit: 69986e19ffcfb9af674ae5180689ab7bbf92ed28\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-895%2Fnewren%2Fort-api-with-empty-implementation-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-895/newren/ort-api-with-empty-implementation-v2\nPull-Request: https://github.com/git/git/pull/895\n\nRange-diff vs v1:\n\n 1:  54ef18182c ! 1:  b9e73975ea merge-ort: barebones API of new merge strategy with empty implementation\n     @@ Commit message\n          can perform a merge between two branches, neither of which match the\n          index or the working tree, without clobbering the index or working tree.\n      \n     +    The next three commits will demonstrate various uses of this new API.\n     +\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Makefile ##\n     @@ merge-ort.h (new)\n      +\n      +\t/*\n      +\t * Additional metadata used by merge_switch_to_result() or future calls\n     -+\t * to merge_inmemory_*().\n     ++\t * to merge_inmemory_*().  Not for external use.\n      +\t */\n     -+\tunsigned _;\n      +\tvoid *priv;\n     ++\tunsigned ate;\n      +};\n      +\n     -+/* rename-detecting three-way merge, no recursion. */\n     ++/* rename-detecting three-way merge with recursive ancestor consolidation. */\n      +void merge_inmemory_recursive(struct merge_options *opt,\n      +\t\t\t      struct commit_list *merge_bases,\n      +\t\t\t      struct commit *side1,\n      +\t\t\t      struct commit *side2,\n      +\t\t\t      struct merge_result *result);\n      +\n     -+/* rename-detecting three-way merge with recursive ancestor consolidation. */\n     ++/* rename-detecting three-way merge, no recursion. */\n      +void merge_inmemory_nonrecursive(struct merge_options *opt,\n      +\t\t\t\t struct tree *merge_base,\n      +\t\t\t\t struct tree *side1,\n 2:  f597609b88 = 2:  a9fff811a2 merge-ort-wrappers: new convience wrappers to mimic the old merge API\n 3:  a1357fb3b3 = 3:  f38d140c15 fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n 4:  06ecea215f = 4:  5f6c97b889 merge,rebase,revert: select ort or recursive by config or environment\n\n-- \ngitgitgadget\n"},{"id":"408412","messageId":"b9e73975eab1f349be678779ff57155feb4c3501.1603731448.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v2.git.git.1603731448.gitgitgadget@gmail.com","subject":"[PATCH v2 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-26T16:57:25Z","receivedAt":"2020-10-26T16:58:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis is the beginning of a new merge strategy.  While there are some API\ndifferences, and the implementation has some differences in behavior, it\nis essentially meant as an eventual drop-in replacement for\nmerge-recursive.c.  However, it is being built to exist side-by-side\nwith merge-recursive so that we have plenty of time to find out how\nthose differences pan out in the real world while people can still fall\nback to merge-recursive.  (Also, I intend to avoid modifying\nmerge-recursive during this process, to keep it stable.)\n\nThe primary difference noticable here is that the updating of the\nworking tree and index is not done simultaneously with the merge\nalgorithm, but is a separate post-processing step.  The new API is\ndesigned so that one can do repeated merges (e.g. during a rebase or\ncherry-pick) and only update the index and working tree one time at the\nend instead of updating it with every intermediate result.  Also, one\ncan perform a merge between two branches, neither of which match the\nindex or the working tree, without clobbering the index or working tree.\n\nThe next three commits will demonstrate various uses of this new API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile    |  1 +\n merge-ort.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n merge-ort.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 102 insertions(+)\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..088770c2ae 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n+LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-recursive.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort.c b/merge-ort.c\nnew file mode 100644\nindex 0000000000..5230364a8d\n--- /dev/null\n+++ b/merge-ort.c\n@@ -0,0 +1,52 @@\n+/*\n+ * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n+ * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n+ * to replace\n+ *\n+ *   git merge [-s recursive]\n+ *\n+ * with\n+ *\n+ *   git merge -s ort\n+ *\n+ * Note: git's parser allows the space between '-s' and its argument to be\n+ * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n+ * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n+ */\n+\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs)\n+{\n+\tdie(\"Not yet implemented\");\n+\tmerge_finalize(opt, result);\n+}\n+\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_inmemory_nonrecursive(struct merge_options *opt,\n+\t\t\t\t struct tree *merge_base,\n+\t\t\t\t struct tree *side1,\n+\t\t\t\t struct tree *side2,\n+\t\t\t\t struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_inmemory_recursive(struct merge_options *opt,\n+\t\t\t      struct commit_list *merge_bases,\n+\t\t\t      struct commit *side1,\n+\t\t\t      struct commit *side2,\n+\t\t\t      struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\ndiff --git a/merge-ort.h b/merge-ort.h\nnew file mode 100644\nindex 0000000000..47d30cf538\n--- /dev/null\n+++ b/merge-ort.h\n@@ -0,0 +1,49 @@\n+#ifndef MERGE_ORT_H\n+#define MERGE_ORT_H\n+\n+#include \"merge-recursive.h\"\n+\n+struct commit;\n+struct tree;\n+\n+struct merge_result {\n+\t/* whether the merge is clean */\n+\tint clean;\n+\n+\t/* Result of merge.  If !clean, represents what would go in worktree */\n+\tstruct tree *tree;\n+\n+\t/*\n+\t * Additional metadata used by merge_switch_to_result() or future calls\n+\t * to merge_inmemory_*().  Not for external use.\n+\t */\n+\tvoid *priv;\n+\tunsigned ate;\n+};\n+\n+/* rename-detecting three-way merge with recursive ancestor consolidation. */\n+void merge_inmemory_recursive(struct merge_options *opt,\n+\t\t\t      struct commit_list *merge_bases,\n+\t\t\t      struct commit *side1,\n+\t\t\t      struct commit *side2,\n+\t\t\t      struct merge_result *result);\n+\n+/* rename-detecting three-way merge, no recursion. */\n+void merge_inmemory_nonrecursive(struct merge_options *opt,\n+\t\t\t\t struct tree *merge_base,\n+\t\t\t\t struct tree *side1,\n+\t\t\t\t struct tree *side2,\n+\t\t\t\t struct merge_result *result);\n+\n+/* Update the working tree and index from head to result after inmemory merge */\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs);\n+\n+/* Do needed cleanup when not calling merge_switch_to_result() */\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408453","messageId":"xmqqa6w8emxn.fsf@gitster.c.googlers.com","threadId":"54475","inReplyTo":"b9e73975eab1f349be678779ff57155feb4c3501.1603731448.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-26T20:45:24Z","receivedAt":"2020-10-26T20:45:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> + *   git merge [-s recursive]\n> + *\n> + * with\n> + *\n> + *   git merge -s ort\n> + *\n> + * Note: git's parser allows the space between '-s' and its argument to be\n> + * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n> + * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n\nOne thing that is quite unpleasant is \"git grep ort\" gives us too\nmany hits already, and it will be hard to locate ort related changes\nwith \"git log --grep=ort\", as the name is too short to serve as an\neffective way to limit the search.\n\n> diff --git a/merge-ort.h b/merge-ort.h\n> new file mode 100644\n> index 0000000000..47d30cf538\n> --- /dev/null\n> +++ b/merge-ort.h\n> @@ -0,0 +1,49 @@\n> +#ifndef MERGE_ORT_H\n> +#define MERGE_ORT_H\n> +\n> +#include \"merge-recursive.h\"\n> +\n> +struct commit;\n> +struct tree;\n> +\n> +struct merge_result {\n> +\t/* whether the merge is clean */\n> +\tint clean;\n> +\n> +\t/* Result of merge.  If !clean, represents what would go in worktree */\n> +\tstruct tree *tree;\n\nCurious.  Because there is no way for \"struct tree\" to hold an\nin-core pointer to a \"struct blob\" (iow, for a blob to be in a\n\"struct tree\", it has to have been assigned an object name), unless\nwe are using the \"pretend\" mechanism, which has its own downsides,\nwe are committed to create a throw-away blob objects with conflict\nmarkers in them, and write them to the object store.\n\nIf we were writing a new merge machinery from scratch, I would have\npreferred a truly in-core implementation that does not have to write\nout to the object store but if this makes the implementation simpler,\nperhaps it is a small enough price to pay.\n\n> +\t/*\n> +\t * Additional metadata used by merge_switch_to_result() or future calls\n> +\t * to merge_inmemory_*().  Not for external use.\n> +\t */\n> +\tvoid *priv;\n> +\tunsigned ate;\n\nI'd prefer to see this named not so cute.  Will we hang random\nvariations of things, or would this be better to be made into a\npointer to union, with an enum that tells us which kind it is in\nuse?\n\n> +};\n\n\n> +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n> +void merge_inmemory_recursive(struct merge_options *opt,\n> +\t\t\t      struct commit_list *merge_bases,\n> +\t\t\t      struct commit *side1,\n> +\t\t\t      struct commit *side2,\n> +\t\t\t      struct merge_result *result);\n\nI've seen \"incore\" spelled as a squashed-into-a-single-word, but not\n\"in_memory\".\n\n> +/* rename-detecting three-way merge, no recursion. */\n> +void merge_inmemory_nonrecursive(struct merge_options *opt,\n> +\t\t\t\t struct tree *merge_base,\n> +\t\t\t\t struct tree *side1,\n> +\t\t\t\t struct tree *side2,\n> +\t\t\t\t struct merge_result *result);\n> +\n> +/* Update the working tree and index from head to result after inmemory merge */\n> +void merge_switch_to_result(struct merge_options *opt,\n> +\t\t\t    struct tree *head,\n> +\t\t\t    struct merge_result *result,\n> +\t\t\t    int update_worktree_and_index,\n> +\t\t\t    int display_update_msgs);\n\nTo those who have known how our merge works, a natural expectation\nfor an \"in-core\" merge is that when the \"in-core\" merge finishes,\nthe index would hold the higher stages for the conflicted paths, and\ncleanly merged paths would have the result at stage 0, and there is\nan extra thing that we haven't had that represents what the working\ntree files for conflicted paths should look like (historically we\nwrote out the conflicted result to the working tree files---being\nin-core operation we cannot afford to), so that (1) cleanly merged\npaths can be externalized by writing from their stage 0 entries and\n(2) contents with conflicts can be externalized by that \"extra\nthing\".\n\nBut this helper says \"working tree and index\" are both updated, so\nthe \"in-core\" merge it expects must have not just the working tree\nresult (in result->tree, as the comment in the structure says) but\nalso how the higher stages of the index should look like somewhere\nin the result structure.  How the latter is done is not at all clear\nat this point in the mock-up.  Leaving it opaque is fine, but the\nfunction, and the result structure, deserve clarification to avoid\nconfusing readers by highlighting how it is different from the\ntraditional ways (e.g. \"we don't touch the index at all---instead we\nstore that in the priv/ate fields\", if that is what is going on).\n\nThanks.\n"},{"id":"408458","messageId":"CABPp-BFtzeOHS=ptgzkrpOL3gwvE6bSaRgxLO1QJ284J08i-mA@mail.gmail.com","threadId":"54475","inReplyTo":"xmqqa6w8emxn.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-26T21:18:46Z","receivedAt":"2020-10-26T21:19:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 26, 2020 at 1:45 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > + *   git merge [-s recursive]\n> > + *\n> > + * with\n> > + *\n> > + *   git merge -s ort\n> > + *\n> > + * Note: git's parser allows the space between '-s' and its argument to be\n> > + * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n> > + * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n>\n> One thing that is quite unpleasant is \"git grep ort\" gives us too\n> many hits already, and it will be hard to locate ort related changes\n> with \"git log --grep=ort\", as the name is too short to serve as an\n> effective way to limit the search.\n\nSuggestions for an alternative name?  merge-pandemic.c since it was\nmostly written during the pandemic?\n\nI'm really not good at naming things...\n\n> > diff --git a/merge-ort.h b/merge-ort.h\n> > new file mode 100644\n> > index 0000000000..47d30cf538\n> > --- /dev/null\n> > +++ b/merge-ort.h\n> > @@ -0,0 +1,49 @@\n> > +#ifndef MERGE_ORT_H\n> > +#define MERGE_ORT_H\n> > +\n> > +#include \"merge-recursive.h\"\n> > +\n> > +struct commit;\n> > +struct tree;\n> > +\n> > +struct merge_result {\n> > +     /* whether the merge is clean */\n> > +     int clean;\n> > +\n> > +     /* Result of merge.  If !clean, represents what would go in worktree */\n> > +     struct tree *tree;\n>\n> Curious.  Because there is no way for \"struct tree\" to hold an\n> in-core pointer to a \"struct blob\" (iow, for a blob to be in a\n> \"struct tree\", it has to have been assigned an object name), unless\n> we are using the \"pretend\" mechanism, which has its own downsides,\n> we are committed to create a throw-away blob objects with conflict\n> markers in them, and write them to the object store.\n\nThis is something merge-recursive already does (and I've copied some\nof that code over, around merge_3way() and the call to\nwrite_object_file() with the results).  I thought the reasoning behind\nthis was memory -- we're okay assuming any given file fits in memory\n(and perhaps up to three copies of it so we can do a three-way merge),\nbut we're not okay assuming all (changed) files from a commit\nsimultaneously fit in memory.\n\n> If we were writing a new merge machinery from scratch, I would have\n> preferred a truly in-core implementation that does not have to write\n> out to the object store but if this makes the implementation simpler,\n> perhaps it is a small enough price to pay.\n\nI thought about that early on, but I was worried about out-of-memory\nsituations if we attempt to do truly in-memory, at least for large\nchanges in large repositories.\n\nAnd as you have seen above, I do rely on being able to create trees.\n\n> > +     /*\n> > +      * Additional metadata used by merge_switch_to_result() or future calls\n> > +      * to merge_inmemory_*().  Not for external use.\n> > +      */\n> > +     void *priv;\n> > +     unsigned ate;\n>\n> I'd prefer to see this named not so cute.  Will we hang random\n> variations of things, or would this be better to be made into a\n> pointer to union, with an enum that tells us which kind it is in\n> use?\n\nI don't understand the union suggestion.  Both fields are used.\n\nWould you object if 'ate' was named '_'?  That was my original name,\nbut Taylor didn't like it.  It is used on about 4 lines of code, I'm\n99.9% sure it will never be used in additional locations, and callers\nshouldn't mess with it.  I just don't have a good name for it.  I\nguess maybe I should just call it \"properly_initialized\" or something.\n\n> > +};\n>\n>\n> > +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n> > +void merge_inmemory_recursive(struct merge_options *opt,\n> > +                           struct commit_list *merge_bases,\n> > +                           struct commit *side1,\n> > +                           struct commit *side2,\n> > +                           struct merge_result *result);\n>\n> I've seen \"incore\" spelled as a squashed-into-a-single-word, but not\n> \"in_memory\".\n\nI can add an underscore.  Or switch to incore.  Preference?\n\n> > +/* rename-detecting three-way merge, no recursion. */\n> > +void merge_inmemory_nonrecursive(struct merge_options *opt,\n> > +                              struct tree *merge_base,\n> > +                              struct tree *side1,\n> > +                              struct tree *side2,\n> > +                              struct merge_result *result);\n> > +\n> > +/* Update the working tree and index from head to result after inmemory merge */\n> > +void merge_switch_to_result(struct merge_options *opt,\n> > +                         struct tree *head,\n> > +                         struct merge_result *result,\n> > +                         int update_worktree_and_index,\n> > +                         int display_update_msgs);\n>\n> To those who have known how our merge works, a natural expectation\n> for an \"in-core\" merge is that when the \"in-core\" merge finishes,\n> the index would hold the higher stages for the conflicted paths, and\n> cleanly merged paths would have the result at stage 0, and there is\n> an extra thing that we haven't had that represents what the working\n> tree files for conflicted paths should look like (historically we\n> wrote out the conflicted result to the working tree files---being\n> in-core operation we cannot afford to), so that (1) cleanly merged\n> paths can be externalized by writing from their stage 0 entries and\n> (2) contents with conflicts can be externalized by that \"extra\n> thing\".\n>\n> But this helper says \"working tree and index\" are both updated, so\n> the \"in-core\" merge it expects must have not just the working tree\n> result (in result->tree, as the comment in the structure says) but\n> also how the higher stages of the index should look like somewhere\n> in the result structure.  How the latter is done is not at all clear\n> at this point in the mock-up.  Leaving it opaque is fine, but the\n> function, and the result structure, deserve clarification to avoid\n> confusing readers by highlighting how it is different from the\n> traditional ways (e.g. \"we don't touch the index at all---instead we\n> store that in the priv/ate fields\", if that is what is going on).\n\nYes, your reading is correct.  We don't touch the index (or any index,\nor any cache_entry) at all.  Among other things, data that can be used\nto update the index are in the \"priv\" field.\n\nI'll try to add some notes to the file.\n"},{"id":"408461","messageId":"20201026215622.2138981-1-jonathantanmy@google.com","threadId":"54475","inReplyTo":"CABPp-BEGo8qfaoz6vUs+ktvmNUZdiHjwda468Qkh5uaD-wAd4w@mail.gmail.com","subject":"Re: [PATCH 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-10-26T21:56:22Z","receivedAt":"2020-10-26T21:56:30Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Hi,\n> \n> On Wed, Oct 21, 2020 at 6:22 AM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > In this series, I try to show the new merge API I have developed in\n> > merge-ort and show how it differs from that provided by merge-recursive. I\n> > do this in four steps, each corresponding to a patch:\n> \n> I should probably call out that even if folks don't have time to\n> review patches, I'm particularly interested in opinions on the\n> following two questions:\n>   * Are the \"pull.twohead\" and \"GIT_TEST_MERGE_ALGORITHM\" names in\n> patch 4 good/bad/ugly?  (especially the mapping from \"pull\" to revert,\n> cherry-pick, rebase, and merge?)\n\nI think GIT_TEST_MERGE_ALGORITHM is fine. I think extending\n\"pull.twohead\" is ugly - perhaps we could get away with not doing\nanything about it for now, and once this feature is ready, we could add\na new config parameter specially for this.\n\n>   * Is it too weird to have a temporary/hidden builtin, in patch 3?\n> If so, what is a good alternative?\n\nAn alternative is to implement it as part of test-tool - see t/helper/\nfor more information.\n\nI'm not very familiar with the merge code, but overall this looks like a\ngood, clear design. Thanks especially for showing how this code can be\nused (in patch 3) and a comparison to the old API (in patch 2).\n"},{"id":"408464","messageId":"xmqqsga0d4f1.fsf@gitster.c.googlers.com","threadId":"54475","inReplyTo":"CABPp-BFtzeOHS=ptgzkrpOL3gwvE6bSaRgxLO1QJ284J08i-mA@mail.gmail.com","subject":"Re: [PATCH v2 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-10-26T22:10:42Z","receivedAt":"2020-10-26T22:10:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> > +     /*\n>> > +      * Additional metadata used by merge_switch_to_result() or future calls\n>> > +      * to merge_inmemory_*().  Not for external use.\n>> > +      */\n>> > +     void *priv;\n>> > +     unsigned ate;\n>>\n>> I'd prefer to see this named not so cute.  Will we hang random\n>> variations of things, or would this be better to be made into a\n>> pointer to union, with an enum that tells us which kind it is in\n>> use?\n>\n> I don't understand the union suggestion.  Both fields are used.\n\nI thought \"priv\" shouldn't be \"anything goes, so it is 'void *'\" but\nis probably a \"union { ... } priv;\" with associated enum next to it\nthat tells which one of the possibilities in the union is in effect.\n\n> Would you object if 'ate' was named '_'?\n\nEither is horrible name.\n\n>> > +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n>> > +void merge_inmemory_recursive(struct merge_options *opt,\n>> > +                           struct commit_list *merge_bases,\n>> > +                           struct commit *side1,\n>> > +                           struct commit *side2,\n>> > +                           struct merge_result *result);\n>>\n>> I've seen \"incore\" spelled as a squashed-into-a-single-word, but not\n>> \"in_memory\".\n>\n> I can add an underscore.  Or switch to incore.  Preference?\n\nAnything shorter would get my vote.\n\n> Yes, your reading is correct.  We don't touch the index (or any index,\n> or any cache_entry) at all.  Among other things, data that can be used\n> to update the index are in the \"priv\" field.\n>\n> I'll try to add some notes to the file.\n\nSounds good.\n"},{"id":"408466","messageId":"CABPp-BEToXsTiF+7C12-aZFYFFxR99j1aGmMAi3VXfGUM_OrXA@mail.gmail.com","threadId":"54475","inReplyTo":"xmqqsga0d4f1.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-26T22:28:24Z","receivedAt":"2020-10-26T22:28:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 26, 2020 at 3:10 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Elijah Newren <newren@gmail.com> writes:\n\n> >> > +     /*\n> >> > +      * Additional metadata used by merge_switch_to_result() or future calls\n> >> > +      * to merge_inmemory_*().  Not for external use.\n> >> > +      */\n> >> > +     void *priv;\n> >> > +     unsigned ate;\n> >>\n> >> I'd prefer to see this named not so cute.  Will we hang random\n> >> variations of things, or would this be better to be made into a\n> >> pointer to union, with an enum that tells us which kind it is in\n> >> use?\n> >\n> > I don't understand the union suggestion.  Both fields are used.\n>\n> I thought \"priv\" shouldn't be \"anything goes, so it is 'void *'\" but\n> is probably a \"union { ... } priv;\" with associated enum next to it\n> that tells which one of the possibilities in the union is in effect.\n\nI guess I'm still not following where these \"possibilities\" come from\nor what I could have said that would have suggested possibilities.\n\npriv is a pointer to a private data structure.  The data structure is\ndefined and used only in merge-ort.c and not exposed to callers.  That\ndata does need to be passed from merge_incore_*() to\nmerge_switch_to_result() (or to merge_finalize()).  Since those two\nare separate function calls invoked by the caller, and since callers\nshouldn't touch any of this data in priv, it's passed as an opaque\nfield within merge_result.  Thus void*.\n\n> > Would you object if 'ate' was named '_'?\n>\n> Either is horrible name.\n\nI'll just rip it out, for now.  It isn't relevant to early versions of\nmerge-ort anyway, and when I re-introduce it with yet another horrible\nname, there will at least be more context for others to suggest a\nbetter name for me.\n\nBesides, the variable isn't at all necessary for the algorithm; it\nexists solely as a way to catch a small gotcha in API usage of later\nversions of merge-ort, which I otherwise didn't have a clean way of\ndetecting.\n\n> >> > +/* rename-detecting three-way merge with recursive ancestor consolidation. */\n> >> > +void merge_inmemory_recursive(struct merge_options *opt,\n> >> > +                           struct commit_list *merge_bases,\n> >> > +                           struct commit *side1,\n> >> > +                           struct commit *side2,\n> >> > +                           struct merge_result *result);\n> >>\n> >> I've seen \"incore\" spelled as a squashed-into-a-single-word, but not\n> >> \"in_memory\".\n> >\n> > I can add an underscore.  Or switch to incore.  Preference?\n>\n> Anything shorter would get my vote.\n\nSounds good, will do.\n\n> > Yes, your reading is correct.  We don't touch the index (or any index,\n> > or any cache_entry) at all.  Among other things, data that can be used\n> > to update the index are in the \"priv\" field.\n> >\n> > I'll try to add some notes to the file.\n>\n> Sounds good.\n\n:-)\n"},{"id":"408473","messageId":"CABPp-BFUS39pmB0S79WfOAiOmeM7gLrvog=b6zYrGRCo_RQr7Q@mail.gmail.com","threadId":"54475","inReplyTo":"20201026215622.2138981-1-jonathantanmy@google.com","subject":"Re: [PATCH 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-27T00:52:06Z","receivedAt":"2020-10-27T00:52:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 26, 2020 at 2:56 PM Jonathan Tan <jonathantanmy@google.com> wrote:\n>\n> > Hi,\n> >\n> > On Wed, Oct 21, 2020 at 6:22 AM Elijah Newren via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> > >\n> > > In this series, I try to show the new merge API I have developed in\n> > > merge-ort and show how it differs from that provided by merge-recursive. I\n> > > do this in four steps, each corresponding to a patch:\n> >\n> > I should probably call out that even if folks don't have time to\n> > review patches, I'm particularly interested in opinions on the\n> > following two questions:\n> >   * Are the \"pull.twohead\" and \"GIT_TEST_MERGE_ALGORITHM\" names in\n> > patch 4 good/bad/ugly?  (especially the mapping from \"pull\" to revert,\n> > cherry-pick, rebase, and merge?)\n>\n> I think GIT_TEST_MERGE_ALGORITHM is fine. I think extending\n> \"pull.twohead\" is ugly - perhaps we could get away with not doing\n> anything about it for now, and once this feature is ready, we could add\n> a new config parameter specially for this.\n\nYeah, it is kinda ugly.  I was thinking of adding \"merge.backend\" when\nI discovered that \"pull.twohead\" already existed.  I didn't know if\ntwo configuration knobs doing the same thing was uglier, or using that\nold ugly misnomer was.  Maybe we can use the merge.backend name or\nsomething like it once merge-ort is further along.\n\n> >   * Is it too weird to have a temporary/hidden builtin, in patch 3?\n> > If so, what is a good alternative?\n>\n> An alternative is to implement it as part of test-tool - see t/helper/\n> for more information.\n\nOh, interesting idea; I hadn't thought of that.  However, It seems\nlike a bit of a misfit there too -- fast-rebase isn't used in the\ntestsuite anywhere and I didn't have plans to add it.  My plan was for\nfast-rebase to go away soon (perhaps as soon as the merge-ort\nimplementation lands), probably long before merge-recursive.c goes\naway.  fast-rebase exists just to (1) demonstrate the API and (2)\ndemonstrate some performance benchmarks I used to guide my performance\ndecisions.  Once merge-ort has fully landed, reason (2) becomes\nobsolete.  Reason (1) might still exist in part, but hopefully we can\nstart updating sequencer.c to use those ideas and then make\nfast-rebase fully obsolete.\n\n> I'm not very familiar with the merge code, but overall this looks like a\n> good, clear design. Thanks especially for showing how this code can be\n> used (in patch 3) and a comparison to the old API (in patch 2).\n\nThanks for taking a look.  :-)\n"},{"id":"408474","messageId":"d7f6a834ab4942b3af329eff9c883fcf7affd7f4.1603764490.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v3.git.git.1603764490.gitgitgadget@gmail.com","subject":"[PATCH v3 2/4] merge-ort-wrappers: new convience wrappers to mimic the old merge API","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-27T02:08:08Z","receivedAt":"2020-10-27T02:08:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are a few differences between the new API in merge-ort and the old\nAPI in merge-recursive.  While the new API is more flexible, it might\nfeel like more work at times than the old API.  merge-ort-wrappers\ncreates two convenience wrappers taking the exact same arguments as the\nold merge_trees() and merge_recursive() functions and implements them\nvia the new API.  This makes converting existing callsites easier, and\nserves to highlight some of the differences in the API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile             |  1 +\n merge-ort-wrappers.c | 62 ++++++++++++++++++++++++++++++++++++++++++++\n merge-ort-wrappers.h | 25 ++++++++++++++++++\n 3 files changed, 88 insertions(+)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n\ndiff --git a/Makefile b/Makefile\nindex 088770c2ae..382fe73c76 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -922,6 +922,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n 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.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nnew file mode 100644\nindex 0000000000..7eec25f93a\n--- /dev/null\n+++ b/merge-ort-wrappers.c\n@@ -0,0 +1,62 @@\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n+\n+#include \"commit.h\"\n+\n+static int unclean(struct merge_options *opt, struct tree *head)\n+{\n+\t/* Sanity check on repo state; index must match head */\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (head && repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\tfprintf(stderr, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *merge_base)\n+{\n+\tstruct merge_result result;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tif (oideq(&merge_base->object.oid, &merge->object.oid)) {\n+\t\tprintf(_(\"Already up to date!\"));\n+\t\treturn 1;\n+\t}\n+\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_incore_nonrecursive(opt, merge_base, head, merge, &result);\n+\tmerge_switch_to_result(opt, head, &result, 1, 1);\n+\n+\treturn result.clean;\n+}\n+\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *side1,\n+\t\t\tstruct commit *side2,\n+\t\t\tstruct commit_list *merge_bases,\n+\t\t\tstruct commit **result)\n+{\n+\tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\n+\tstruct merge_result tmp;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tmemset(&tmp, 0, sizeof(tmp));\n+\tmerge_incore_recursive(opt, merge_bases, side1, side2, &tmp);\n+\tmerge_switch_to_result(opt, head, &tmp, 1, 1);\n+\t*result = NULL;\n+\n+\treturn tmp.clean;\n+}\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nnew file mode 100644\nindex 0000000000..0c4c57adbb\n--- /dev/null\n+++ b/merge-ort-wrappers.h\n@@ -0,0 +1,25 @@\n+#ifndef MERGE_ORT_WRAPPERS_H\n+#define MERGE_ORT_WRAPPERS_H\n+\n+#include \"merge-recursive.h\"\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * Wrapper mimicking the old merge_trees() function.\n+ */\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *common);\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * Wrapper mimicking the old merge_recursive() function.\n+ */\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *h1,\n+\t\t\tstruct commit *h2,\n+\t\t\tstruct commit_list *ancestors,\n+\t\t\tstruct commit **result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408475","messageId":"pull.895.v3.git.git.1603764490.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v2.git.git.1603731448.gitgitgadget@gmail.com","subject":"[PATCH v3 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-27T02:08:06Z","receivedAt":"2020-10-27T02:08:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In this series, I show the new merge API I have developed in merge-ort, and\nshow how it differs from that provided by merge-recursive. I do this in four\nsteps, each corresponding to a patch.\n\nChanges since v2:\n\n * Extended comments in struct merge_result and in front of function\n   definitions to make API usage clearer; in particular, noting how this\n   merge backend differs in not using the index (and only updating it as\n   part of merge_switch_to_result()).\n * Drop the other private field of struct merge_result, for now.\n * Rename merge_inmemory_* to merge_incore_*\n\nElijah Newren (4):\n  merge-ort: barebones API of new merge strategy with empty\n    implementation\n  merge-ort-wrappers: new convience wrappers to mimic the old merge API\n  fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n  merge,rebase,revert: select ort or recursive by config or environment\n\n Makefile              |   3 +\n builtin.h             |   1 +\n builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n builtin/merge.c       |  26 +++++-\n builtin/rebase.c      |   9 +-\n builtin/revert.c      |   2 +\n git.c                 |   1 +\n merge-ort-wrappers.c  |  62 +++++++++++++\n merge-ort-wrappers.h  |  25 +++++\n merge-ort.c           |  52 +++++++++++\n merge-ort.h           |  58 ++++++++++++\n sequencer.c           |  71 +++++++++++---\n 12 files changed, 505 insertions(+), 15 deletions(-)\n create mode 100644 builtin/fast-rebase.c\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\n\nbase-commit: 69986e19ffcfb9af674ae5180689ab7bbf92ed28\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-895%2Fnewren%2Fort-api-with-empty-implementation-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-895/newren/ort-api-with-empty-implementation-v3\nPull-Request: https://github.com/git/git/pull/895\n\nRange-diff vs v2:\n\n 1:  b9e73975ea ! 1:  3357ea415e merge-ort: barebones API of new merge strategy with empty implementation\n     @@ merge-ort.c (new)\n      +\tdie(\"Not yet implemented\");\n      +}\n      +\n     -+void merge_inmemory_nonrecursive(struct merge_options *opt,\n     -+\t\t\t\t struct tree *merge_base,\n     -+\t\t\t\t struct tree *side1,\n     -+\t\t\t\t struct tree *side2,\n     -+\t\t\t\t struct merge_result *result)\n     ++void merge_incore_nonrecursive(struct merge_options *opt,\n     ++\t\t\t       struct tree *merge_base,\n     ++\t\t\t       struct tree *side1,\n     ++\t\t\t       struct tree *side2,\n     ++\t\t\t       struct merge_result *result)\n      +{\n      +\tdie(\"Not yet implemented\");\n      +}\n      +\n     -+void merge_inmemory_recursive(struct merge_options *opt,\n     -+\t\t\t      struct commit_list *merge_bases,\n     -+\t\t\t      struct commit *side1,\n     -+\t\t\t      struct commit *side2,\n     -+\t\t\t      struct merge_result *result)\n     ++void merge_incore_recursive(struct merge_options *opt,\n     ++\t\t\t    struct commit_list *merge_bases,\n     ++\t\t\t    struct commit *side1,\n     ++\t\t\t    struct commit *side2,\n     ++\t\t\t    struct merge_result *result)\n      +{\n      +\tdie(\"Not yet implemented\");\n      +}\n     @@ merge-ort.h (new)\n      +struct tree;\n      +\n      +struct merge_result {\n     -+\t/* whether the merge is clean */\n     ++\t/* Whether the merge is clean */\n      +\tint clean;\n      +\n     -+\t/* Result of merge.  If !clean, represents what would go in worktree */\n     ++\t/*\n     ++\t * Result of merge.  If !clean, represents what would go in worktree\n     ++\t * (thus possibly including files containing conflict markers).\n     ++\t */\n      +\tstruct tree *tree;\n      +\n      +\t/*\n      +\t * Additional metadata used by merge_switch_to_result() or future calls\n     -+\t * to merge_inmemory_*().  Not for external use.\n     ++\t * to merge_incore_*().  Includes data needed to update the index (if\n     ++\t * !clean) and to print \"CONFLICT\" messages.  Not for external use.\n      +\t */\n      +\tvoid *priv;\n     -+\tunsigned ate;\n      +};\n      +\n     -+/* rename-detecting three-way merge with recursive ancestor consolidation. */\n     -+void merge_inmemory_recursive(struct merge_options *opt,\n     -+\t\t\t      struct commit_list *merge_bases,\n     -+\t\t\t      struct commit *side1,\n     -+\t\t\t      struct commit *side2,\n     -+\t\t\t      struct merge_result *result);\n     ++/*\n     ++ * rename-detecting three-way merge with recursive ancestor consolidation.\n     ++ * working tree and index are untouched.\n     ++ */\n     ++void merge_incore_recursive(struct merge_options *opt,\n     ++\t\t\t    struct commit_list *merge_bases,\n     ++\t\t\t    struct commit *side1,\n     ++\t\t\t    struct commit *side2,\n     ++\t\t\t    struct merge_result *result);\n      +\n     -+/* rename-detecting three-way merge, no recursion. */\n     -+void merge_inmemory_nonrecursive(struct merge_options *opt,\n     -+\t\t\t\t struct tree *merge_base,\n     -+\t\t\t\t struct tree *side1,\n     -+\t\t\t\t struct tree *side2,\n     -+\t\t\t\t struct merge_result *result);\n     ++/*\n     ++ * rename-detecting three-way merge, no recursion.\n     ++ * working tree and index are untouched.\n     ++ */\n     ++void merge_incore_nonrecursive(struct merge_options *opt,\n     ++\t\t\t       struct tree *merge_base,\n     ++\t\t\t       struct tree *side1,\n     ++\t\t\t       struct tree *side2,\n     ++\t\t\t       struct merge_result *result);\n      +\n     -+/* Update the working tree and index from head to result after inmemory merge */\n     ++/* Update the working tree and index from head to result after incore merge */\n      +void merge_switch_to_result(struct merge_options *opt,\n      +\t\t\t    struct tree *head,\n      +\t\t\t    struct merge_result *result,\n 2:  a9fff811a2 ! 2:  d7f6a834ab merge-ort-wrappers: new convience wrappers to mimic the old merge API\n     @@ merge-ort-wrappers.c (new)\n      +\t}\n      +\n      +\tmemset(&result, 0, sizeof(result));\n     -+\tmerge_inmemory_nonrecursive(opt, merge_base, head, merge, &result);\n     ++\tmerge_incore_nonrecursive(opt, merge_base, head, merge, &result);\n      +\tmerge_switch_to_result(opt, head, &result, 1, 1);\n      +\n      +\treturn result.clean;\n     @@ merge-ort-wrappers.c (new)\n      +\t\treturn -1;\n      +\n      +\tmemset(&tmp, 0, sizeof(tmp));\n     -+\tmerge_inmemory_recursive(opt, merge_bases, side1, side2, &tmp);\n     ++\tmerge_incore_recursive(opt, merge_bases, side1, side2, &tmp);\n      +\tmerge_switch_to_result(opt, head, &tmp, 1, 1);\n      +\t*result = NULL;\n      +\n 3:  f38d140c15 ! 3:  27ad756600 fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n     @@ builtin/fast-rebase.c (new)\n      +\t\tmerge_opt.branch2 = short_commit_name(commit);\n      +\t\tmerge_opt.ancestor = xstrfmt(\"parent of %s\", merge_opt.branch2);\n      +\n     -+\t\tmerge_inmemory_nonrecursive(&merge_opt,\n     -+\t\t\t\t\t    base_tree,\n     -+\t\t\t\t\t    result.tree,\n     -+\t\t\t\t\t    next_tree,\n     -+\t\t\t\t\t    &result);\n     ++\t\tmerge_incore_nonrecursive(&merge_opt,\n     ++\t\t\t\t\t  base_tree,\n     ++\t\t\t\t\t  result.tree,\n     ++\t\t\t\t\t  next_tree,\n     ++\t\t\t\t\t  &result);\n      +\n      +\t\tfree((char*)merge_opt.ancestor);\n      +\t\tmerge_opt.ancestor = NULL;\n 4:  5f6c97b889 ! 4:  0479d59c33 merge,rebase,revert: select ort or recursive by config or environment\n     @@ sequencer.c: static int do_recursive_merge(struct repository *r,\n      -\tstrbuf_release(&o.obuf);\n      +\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n      +\t\tmemset(&result, 0, sizeof(result));\n     -+\t\tmerge_inmemory_nonrecursive(&o, base_tree, head_tree, next_tree,\n     ++\t\tmerge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,\n      +\t\t\t\t\t    &result);\n      +\t\tshow_output = !is_rebase_i(opts) || !result.clean;\n      +\t\t/*\n     @@ sequencer.c: static int do_merge(struct repository *r,\n      -\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n      +\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n      +\t\t/*\n     -+\t\t * TODO: Should use merge_inmemory_recursive() and\n     ++\t\t * TODO: Should use merge_incore_recursive() and\n      +\t\t * merge_switch_to_result(), skipping the call to\n      +\t\t * merge_switch_to_result() when we don't actually need to\n      +\t\t * update the index and working copy immediately.\n\n-- \ngitgitgadget\n"},{"id":"408476","messageId":"3357ea415e3437966f15bf73fbbeb21cda3df592.1603764490.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v3.git.git.1603764490.gitgitgadget@gmail.com","subject":"[PATCH v3 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-27T02:08:07Z","receivedAt":"2020-10-27T02:08:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis is the beginning of a new merge strategy.  While there are some API\ndifferences, and the implementation has some differences in behavior, it\nis essentially meant as an eventual drop-in replacement for\nmerge-recursive.c.  However, it is being built to exist side-by-side\nwith merge-recursive so that we have plenty of time to find out how\nthose differences pan out in the real world while people can still fall\nback to merge-recursive.  (Also, I intend to avoid modifying\nmerge-recursive during this process, to keep it stable.)\n\nThe primary difference noticable here is that the updating of the\nworking tree and index is not done simultaneously with the merge\nalgorithm, but is a separate post-processing step.  The new API is\ndesigned so that one can do repeated merges (e.g. during a rebase or\ncherry-pick) and only update the index and working tree one time at the\nend instead of updating it with every intermediate result.  Also, one\ncan perform a merge between two branches, neither of which match the\nindex or the working tree, without clobbering the index or working tree.\n\nThe next three commits will demonstrate various uses of this new API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile    |  1 +\n merge-ort.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++\n merge-ort.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 111 insertions(+)\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..088770c2ae 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n+LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-recursive.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort.c b/merge-ort.c\nnew file mode 100644\nindex 0000000000..b487901d3e\n--- /dev/null\n+++ b/merge-ort.c\n@@ -0,0 +1,52 @@\n+/*\n+ * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n+ * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n+ * to replace\n+ *\n+ *   git merge [-s recursive]\n+ *\n+ * with\n+ *\n+ *   git merge -s ort\n+ *\n+ * Note: git's parser allows the space between '-s' and its argument to be\n+ * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n+ * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n+ */\n+\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs)\n+{\n+\tdie(\"Not yet implemented\");\n+\tmerge_finalize(opt, result);\n+}\n+\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_incore_nonrecursive(struct merge_options *opt,\n+\t\t\t       struct tree *merge_base,\n+\t\t\t       struct tree *side1,\n+\t\t\t       struct tree *side2,\n+\t\t\t       struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_incore_recursive(struct merge_options *opt,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct commit *side1,\n+\t\t\t    struct commit *side2,\n+\t\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\ndiff --git a/merge-ort.h b/merge-ort.h\nnew file mode 100644\nindex 0000000000..74adccad16\n--- /dev/null\n+++ b/merge-ort.h\n@@ -0,0 +1,58 @@\n+#ifndef MERGE_ORT_H\n+#define MERGE_ORT_H\n+\n+#include \"merge-recursive.h\"\n+\n+struct commit;\n+struct tree;\n+\n+struct merge_result {\n+\t/* Whether the merge is clean */\n+\tint clean;\n+\n+\t/*\n+\t * Result of merge.  If !clean, represents what would go in worktree\n+\t * (thus possibly including files containing conflict markers).\n+\t */\n+\tstruct tree *tree;\n+\n+\t/*\n+\t * Additional metadata used by merge_switch_to_result() or future calls\n+\t * to merge_incore_*().  Includes data needed to update the index (if\n+\t * !clean) and to print \"CONFLICT\" messages.  Not for external use.\n+\t */\n+\tvoid *priv;\n+};\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * working tree and index are untouched.\n+ */\n+void merge_incore_recursive(struct merge_options *opt,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct commit *side1,\n+\t\t\t    struct commit *side2,\n+\t\t\t    struct merge_result *result);\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * working tree and index are untouched.\n+ */\n+void merge_incore_nonrecursive(struct merge_options *opt,\n+\t\t\t       struct tree *merge_base,\n+\t\t\t       struct tree *side1,\n+\t\t\t       struct tree *side2,\n+\t\t\t       struct merge_result *result);\n+\n+/* Update the working tree and index from head to result after incore merge */\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs);\n+\n+/* Do needed cleanup when not calling merge_switch_to_result() */\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408477","messageId":"27ad7566007676fc69dc28aa3b289547f0ccabca.1603764490.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v3.git.git.1603764490.gitgitgadget@gmail.com","subject":"[PATCH v3 3/4] fast-rebase: demonstrate merge-ort's API via temporary/hidden command","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-27T02:08:09Z","receivedAt":"2020-10-27T02:08:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd a special built-in that is only of use to git-developers and only\nduring the development of merge-ort, and which is designed to\nimmediately fail and print:\n   git: 'fast-rebase' is not a git command\nunless a special GIT_TEST_MERGE_ALGORITHM environment variable is set.\n\nThis special builtin serves two purposes:\n\n  1) Demonstrate the desired API of merge-ort.  In particular,\n     fast-rebase takes advantage of the separation of the merging\n     operation from the updating of the index and working tree, to\n     allow it to pick N commits, but only update the index and working\n     tree once at the end.  Look for the calls to\n     merge_inmemory_nonrecursive() and merge_switch_to_result().\n\n  2) Provide a convenient benchmark that isn't polluted by the heavy\n     disk writing and forking of unnecessary processes that comes from\n     sequencer.c and merge-recursive.c.  fast-rebase is not meant to\n     replace sequencer.c, just give ideas on how sequencer.c can be\n     changed.  Updating sequencer.c with these goals is probably a\n     large amount of work; writing a simple targeted command with\n     no documentation, less-than-useful help messages, numerous\n     limitations in terms of flags it can accept and situations it can\n     handle, and which is flagged off from users is a much easier\n     interim step.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile              |   1 +\n builtin.h             |   1 +\n builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n git.c                 |   1 +\n 4 files changed, 213 insertions(+)\n create mode 100644 builtin/fast-rebase.c\n\ndiff --git a/Makefile b/Makefile\nindex 382fe73c76..1b40d780fa 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1087,6 +1087,7 @@ BUILTIN_OBJS += builtin/difftool.o\n BUILTIN_OBJS += builtin/env--helper.o\n BUILTIN_OBJS += builtin/fast-export.o\n BUILTIN_OBJS += builtin/fast-import.o\n+BUILTIN_OBJS += builtin/fast-rebase.o\n BUILTIN_OBJS += builtin/fetch-pack.o\n BUILTIN_OBJS += builtin/fetch.o\n BUILTIN_OBJS += builtin/fmt-merge-msg.o\ndiff --git a/builtin.h b/builtin.h\nindex 53fb290963..75ff7dc8a9 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -151,6 +151,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix);\n int cmd_env__helper(int argc, const char **argv, const char *prefix);\n int cmd_fast_export(int argc, const char **argv, const char *prefix);\n int cmd_fast_import(int argc, const char **argv, const char *prefix);\n+int cmd_fast_rebase(int argc, const char **argv, const char *prefix);\n int cmd_fetch(int argc, const char **argv, const char *prefix);\n int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/fast-rebase.c b/builtin/fast-rebase.c\nnew file mode 100644\nindex 0000000000..b69632054f\n--- /dev/null\n+++ b/builtin/fast-rebase.c\n@@ -0,0 +1,210 @@\n+/*\n+ * \"git fast-rebase\" builtin command\n+ *\n+ * FAST: Forking Any Subprocesses (is) Taboo\n+ *\n+ * This is meant SOLELY as a demo of what is possible.  sequencer.c and\n+ * rebase.c should be refactored to use the ideas here, rather than attempting\n+ * to extend this file to replace those (unless Phillip or Dscho say that\n+ * refactoring is too hard and we need a clean slate, but I'm guessing that\n+ * refactoring is the better route).\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"builtin.h\"\n+\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"lockfile.h\"\n+#include \"merge-ort.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"sequencer.h\"\n+#include \"strvec.h\"\n+#include \"tree.h\"\n+\n+static const char *short_commit_name(struct commit *commit)\n+{\n+\treturn find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);\n+}\n+\n+static struct commit *peel_committish(const char *name)\n+{\n+\tstruct object *obj;\n+\tstruct object_id oid;\n+\n+\tif (get_oid(name, &oid))\n+\t\treturn NULL;\n+\tobj = parse_object(the_repository, &oid);\n+\treturn (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);\n+}\n+\n+static char *get_author(const char *message)\n+{\n+\tsize_t len;\n+\tconst char *a;\n+\n+\ta = find_commit_header(message, \"author\", &len);\n+\tif (a)\n+\t\treturn xmemdupz(a, len);\n+\n+\treturn NULL;\n+}\n+\n+static struct commit *create_commit(struct tree *tree,\n+\t\t\t\t    struct commit *based_on,\n+\t\t\t\t    struct commit *parent)\n+{\n+\tstruct object_id ret;\n+\tstruct object *obj;\n+\tstruct commit_list *parents = NULL;\n+\tchar *author;\n+\tchar *sign_commit = NULL;\n+\tstruct commit_extra_header *extra;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *out_enc = get_commit_output_encoding();\n+\tconst char *message = logmsg_reencode(based_on, NULL, out_enc);\n+\tconst char *orig_message = NULL;\n+\tconst char *exclude_gpgsig[] = { \"gpgsig\", NULL };\n+\n+\tcommit_list_insert(parent, &parents);\n+\textra = read_commit_extra_headers(based_on, exclude_gpgsig);\n+\tfind_commit_subject(message, &orig_message);\n+\tstrbuf_addstr(&msg, orig_message);\n+\tauthor = get_author(message);\n+\treset_ident_date();\n+\tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n+\t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n+\t\terror(_(\"failed to write commit object\"));\n+\t\treturn NULL;\n+\t}\n+\tfree(author);\n+\tstrbuf_release(&msg);\n+\n+\tobj = parse_object(the_repository, &ret);\n+\treturn (struct commit *)obj;\n+}\n+\n+int cmd_fast_rebase(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct commit *onto;\n+\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n+\tstruct object_id head;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint clean = 1;\n+\tstruct strvec rev_walk_args = STRVEC_INIT;\n+\tstruct rev_info revs;\n+\tstruct commit *commit;\n+\tstruct merge_options merge_opt;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n+\tstruct merge_result result;\n+\tstruct strbuf reflog_msg = STRBUF_INIT;\n+\tstruct strbuf branch_name = STRBUF_INIT;\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n+\t\tprintf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n+\t\texit(129);\n+\t}\n+\n+\tif (!getenv(\"GIT_TEST_MERGE_ALGORITHM\")) {\n+\t\tfprintf_ln(stderr, _(\"git: 'fast-rebase' is not a git command. See 'git --help'.\"));\n+\t\texit(1);\n+\t}\n+\n+\tif (argc != 5 || strcmp(argv[1], \"--onto\"))\n+\t\tdie(\"usage: read the code, figure out how to use it, then do so\");\n+\n+\tonto = peel_committish(argv[2]);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\n+\t/* Sanity check */\n+\tif (get_oid(\"HEAD\", &head))\n+\t\tdie(_(\"Cannot read HEAD\"));\n+\tassert(oideq(&onto->object.oid, &head));\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\tassert(repo_read_index(the_repository) >= 0);\n+\n+\trepo_init_revisions(the_repository, &revs, NULL);\n+\trevs.verbose_header = 1;\n+\trevs.max_parents = 1;\n+\trevs.cherry_mark = 1;\n+\trevs.limited = 1;\n+\trevs.reverse = 1;\n+\trevs.right_only = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[4], \"--not\", argv[3], NULL);\n+\n+\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1)\n+\t\treturn error(_(\"unhandled options\"));\n+\n+\tstrvec_clear(&rev_walk_args);\n+\n+\tif (prepare_revision_walk(&revs) < 0)\n+\t\treturn error(_(\"error preparing revisions\"));\n+\n+\tinit_merge_options(&merge_opt, the_repository);\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_opt.show_rename_progress = 1;\n+\tmerge_opt.branch1 = \"HEAD\";\n+\thead_tree = get_commit_tree(onto);\n+\tresult.tree = head_tree;\n+\tlast_commit = onto;\n+\twhile ((commit = get_revision(&revs))) {\n+\t\tstruct commit *base;\n+\n+\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n+\t\t\toid_to_hex(&commit->object.oid));\n+\t\tassert(commit->parents && !commit->parents->next);\n+\t\tbase = commit->parents->item;\n+\n+\t\tnext_tree = get_commit_tree(commit);\n+\t\tbase_tree = get_commit_tree(base);\n+\n+\t\tmerge_opt.branch2 = short_commit_name(commit);\n+\t\tmerge_opt.ancestor = xstrfmt(\"parent of %s\", merge_opt.branch2);\n+\n+\t\tmerge_incore_nonrecursive(&merge_opt,\n+\t\t\t\t\t  base_tree,\n+\t\t\t\t\t  result.tree,\n+\t\t\t\t\t  next_tree,\n+\t\t\t\t\t  &result);\n+\n+\t\tfree((char*)merge_opt.ancestor);\n+\t\tmerge_opt.ancestor = NULL;\n+\t\tif (!result.clean)\n+\t\t\tdie(\"Aborting: Hit a conflict and restarting is not implemented.\");\n+\t\tlast_picked_commit = commit;\n+\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n+\t}\n+\tfprintf(stderr, \"\\nDone.\\n\");\n+\t/* TODO: There should be some kind of rev_info_free(&revs) call... */\n+\tmemset(&revs, 0, sizeof(revs));\n+\n+\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\n+\tif (result.clean < 0)\n+\t\texit(128);\n+\n+\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n+\t\t    oid_to_hex(&last_picked_commit->object.oid),\n+\t\t    oid_to_hex(&last_commit->object.oid));\n+\tif (update_ref(reflog_msg.buf, branch_name.buf,\n+\t\t       &last_commit->object.oid,\n+\t\t       &last_picked_commit->object.oid,\n+\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n+\t\terror(_(\"could not update %s\"), argv[4]);\n+\t\tdie(\"Failed to update %s\", argv[4]);\n+\t}\n+\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n+\t\tdie(_(\"unable to update HEAD\"));\n+\tstrbuf_release(&reflog_msg);\n+\tstrbuf_release(&branch_name);\n+\n+\tprime_cache_tree(the_repository, the_repository->index, result.tree);\n+\tif (write_locked_index(&the_index, &lock,\n+\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n+\t\tdie(_(\"unable to write %s\"), get_index_file());\n+\treturn (clean == 0);\n+}\ndiff --git a/git.c b/git.c\nindex 4bdcdad2cc..af84f11e69 100644\n--- a/git.c\n+++ b/git.c\n@@ -512,6 +512,7 @@ static struct cmd_struct commands[] = {\n \t{ \"env--helper\", cmd_env__helper },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n \t{ \"fast-import\", cmd_fast_import, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"fast-rebase\", cmd_fast_rebase, RUN_SETUP /* | NEED_WORK_TREE */ },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP | NO_PARSEOPT },\n \t{ \"fmt-merge-msg\", cmd_fmt_merge_msg, RUN_SETUP },\n-- \ngitgitgadget\n\n"},{"id":"408478","messageId":"0479d59c33b4bc7543b6a2a5e7c10774f7017dba.1603764490.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v3.git.git.1603764490.gitgitgadget@gmail.com","subject":"[PATCH v3 4/4] merge,rebase,revert: select ort or recursive by config or environment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-27T02:08:10Z","receivedAt":"2020-10-27T02:08:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAllow the testsuite to run where it treats requests for \"recursive\" or\nthe default merge algorithm via consulting the environment variable\nGIT_TEST_MERGE_ALGORITHM which is expected to either be \"recursive\" (the\nold traditional algorithm) or \"ort\" (the new algorithm).\n\nAlso, allow folks to pick the new algorithm via config setting.  It\nturns out builtin/merge.c already had a way to allow users to specify a\ndifferent default merge algorithm: pull.twohead.  Rather odd\nconfiguration name (especially to be in the 'pull' namespace rather than\n'merge') but it's there.  Add that same configuration to rebase,\ncherry-pick, and revert.\n\nThis required updating the various callsites that called merge_trees()\nor merge_recursive() to conditionally call the new API, so this serves\nas another demonstration of what the new API looks and feels like.\nThere are almost certainly some callsites that have not yet been\nmodified to work with the new merge algorithm, but this represents the\nones that I have been testing with thus far.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge.c  | 26 ++++++++++++++++--\n builtin/rebase.c |  9 +++++-\n builtin/revert.c |  2 ++\n sequencer.c      | 71 ++++++++++++++++++++++++++++++++++++++++--------\n 4 files changed, 93 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..87dfc9bc06 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -28,6 +28,7 @@\n #include \"rerere.h\"\n #include \"help.h\"\n #include \"merge-recursive.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n #include \"fmt-merge-msg.h\"\n@@ -88,6 +89,7 @@ static int no_verify;\n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n \t{ \"octopus\",    DEFAULT_OCTOPUS },\n+\t{ \"ort\",        NO_TRIVIAL },\n \t{ \"resolve\",    0 },\n \t{ \"ours\",       NO_FAST_FORWARD | NO_TRIVIAL },\n \t{ \"subtree\",    NO_FAST_FORWARD | NO_TRIVIAL },\n@@ -159,10 +161,17 @@ static struct strategy *get_strategy(const char *name)\n \tstruct strategy *ret;\n \tstatic struct cmdnames main_cmds, other_cmds;\n \tstatic int loaded;\n+\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n \tif (!name)\n \t\treturn NULL;\n \n+\tif (default_strategy &&\n+\t    !strcmp(default_strategy, \"ort\") &&\n+\t    !strcmp(name, \"recursive\")) {\n+\t\tname = \"ort\";\n+\t}\n+\n \tfor (i = 0; i < ARRAY_SIZE(all_strategy); i++)\n \t\tif (!strcmp(name, all_strategy[i].name))\n \t\t\treturn &all_strategy[i];\n@@ -701,7 +710,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)\n \t\treturn error(_(\"Unable to write index.\"));\n \n-\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\")) {\n+\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n+\t    !strcmp(strategy, \"ort\")) {\n \t\tstruct lock_file lock = LOCK_INIT;\n \t\tint clean, x;\n \t\tstruct commit *result;\n@@ -732,8 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\tcommit_list_insert(j->item, &reversed);\n \n \t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n-\t\tclean = merge_recursive(&o, head,\n-\t\t\t\tremoteheads->item, reversed, &result);\n+\t\tif (!strcmp(strategy, \"ort\"))\n+\t\t\tclean = merge_ort_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\t    reversed, &result);\n+\t\telse\n+\t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\treversed, &result);\n \t\tif (clean < 0)\n \t\t\texit(128);\n \t\tif (write_locked_index(&the_index, &lock,\n@@ -1264,6 +1278,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (branch)\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n \n+\tif (!pull_twohead) {\n+\t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n+\t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n+\t\t\tpull_twohead = \"ort\";\n+\t}\n+\n \tinit_diff_ui_defaults();\n \tgit_config(git_merge_config, NULL);\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex eeca53382f..9719aa25da 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -119,6 +119,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \treplay.action = REPLAY_INTERACTIVE_REBASE;\n+\treplay.strategy = NULL;\n \tsequencer_init_config(&replay);\n \n \treplay.signoff = opts->signoff;\n@@ -136,7 +137,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n-\treplay.strategy = opts->strategy;\n+\tif (opts->strategy)\n+\t\treplay.strategy = opts->strategy;\n \n \tif (opts->strategy_opts)\n \t\tparse_strategy_opts(&replay, opts->strategy_opts);\n@@ -1771,6 +1773,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t    options.default_backend);\n \t}\n \n+\tif (options.type == REBASE_MERGE &&\n+\t    !options.strategy &&\n+\t    getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\toptions.strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_PRESERVE_MERGES:\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex f61cc5d82c..c7cb0c1a18 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -202,6 +202,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t/* These option values will be free()d */\n \topts->gpg_sign = xstrdup_or_null(opts->gpg_sign);\n \topts->strategy = xstrdup_or_null(opts->strategy);\n+\tif (!opts->strategy && getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\topts->strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n \n \tif (cmd == 'q') {\n \t\tint ret = sequencer_remove_state(opts);\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..e16d85380c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -14,7 +14,8 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n-#include \"merge-recursive.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"refs.h\"\n #include \"strvec.h\"\n #include \"quote.h\"\n@@ -204,6 +205,20 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (!opts->strategy && !strcmp(k, \"pull.twohead\")) {\n+\t\tint ret = git_config_string((const char**)&opts->strategy, k, v);\n+\t\tif (ret == 0) {\n+\t\t\t/*\n+\t\t\t * pull.twohead is allowed to be multi-valued; we only\n+\t\t\t * care about the first value.\n+\t\t\t */\n+\t\t\tchar *tmp = strchr(opts->strategy, ' ');\n+\t\t\tif (tmp)\n+\t\t\t\t*tmp = '\\0';\n+\t\t}\n+\t\treturn ret;\n+\t}\n+\n \tstatus = git_gpg_config(k, v, NULL);\n \tif (status)\n \t\treturn status;\n@@ -595,8 +610,9 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n+\tstruct merge_result result;\n \tstruct tree *next_tree, *base_tree, *head_tree;\n-\tint clean;\n+\tint clean, show_output;\n \tint i;\n \tstruct lock_file index_lock = LOCK_INIT;\n \n@@ -620,12 +636,27 @@ static int do_recursive_merge(struct repository *r,\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tparse_merge_opt(&o, opts->xopts[i]);\n \n-\tclean = merge_trees(&o,\n-\t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree);\n-\tif (is_rebase_i(opts) && clean <= 0)\n-\t\tfputs(o.obuf.buf, stdout);\n-\tstrbuf_release(&o.obuf);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\tmemset(&result, 0, sizeof(result));\n+\t\tmerge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,\n+\t\t\t\t\t    &result);\n+\t\tshow_output = !is_rebase_i(opts) || !result.clean;\n+\t\t/*\n+\t\t * TODO: merge_switch_to_result will update index/working tree;\n+\t\t * we only really want to do that if !result.clean || this is\n+\t\t * the final patch to be picked.  But determining this is the\n+\t\t * final patch would take some work, and \"head_tree\" would need\n+\t\t * to be replace with the tree the index matched before we\n+\t\t * started doing any picks.\n+\t\t */\n+\t\tmerge_switch_to_result(&o, head_tree, &result, 1, show_output);\n+\t\tclean = result.clean;\n+\t} else {\n+\t\tclean = merge_trees(&o, head_tree, next_tree, base_tree);\n+\t\tif (is_rebase_i(opts) && clean <= 0)\n+\t\t\tfputs(o.obuf.buf, stdout);\n+\t\tstrbuf_release(&o.obuf);\n+\t}\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n@@ -1991,7 +2022,10 @@ static int do_pick_commit(struct repository *r,\n \n \tif (is_rebase_i(opts) && write_author_script(msg.message) < 0)\n \t\tres = -1;\n-\telse if (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || command == TODO_REVERT) {\n+\telse if (!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\") ||\n+\t\t command == TODO_REVERT) {\n \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n \t\t\t\t\t &head, &msgbuf, opts);\n \t\tif (res < 0)\n@@ -3485,7 +3519,9 @@ static int do_merge(struct repository *r,\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts_nr &&\n-\t\t(!opts->strategy || !strcmp(opts->strategy, \"recursive\")) ?\n+\t\t(!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\")) ?\n \t\tNULL : opts->strategy;\n \tstruct merge_options o;\n \tint merge_arg_len, oneline_offset, can_fast_forward, ret, k;\n@@ -3722,7 +3758,20 @@ static int do_merge(struct repository *r,\n \to.branch2 = ref_name.buf;\n \to.buffer_output = 2;\n \n-\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\t/*\n+\t\t * TODO: Should use merge_incore_recursive() and\n+\t\t * merge_switch_to_result(), skipping the call to\n+\t\t * merge_switch_to_result() when we don't actually need to\n+\t\t * update the index and working copy immediately.\n+\t\t */\n+\t\tret = merge_ort_recursive(&o,\n+\t\t\t\t\t  head_commit, merge_commit, reversed,\n+\t\t\t\t\t  &i);\n+\t} else {\n+\t\tret = merge_recursive(&o, head_commit, merge_commit, reversed,\n+\t\t\t\t      &i);\n+\t}\n \tif (ret <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \ngitgitgadget\n"},{"id":"408479","messageId":"CAPig+cTkxP=aaCi8YrxbSYptkgwSYG_sd+P6FiXmtA6xuXxGSw@mail.gmail.com","threadId":"54475","inReplyTo":"3357ea415e3437966f15bf73fbbeb21cda3df592.1603764490.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-10-27T02:33:16Z","receivedAt":"2020-10-27T02:33:32Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Mon, Oct 26, 2020 at 10:08 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> + * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n> + * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n> + * to replace\n> + *\n> + *   git merge [-s recursive]\n> + *\n> + * with\n> + *\n> + *   git merge -s ort\n> + *\n> + * Note: git's parser allows the space between '-s' and its argument to be\n> + * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n> + * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n\nYou forgot:\n\n    git merge -s newren\n\nthe \"NEW Recursive ENgine\" merge strategy. I won't bore you with some\nother backronyms I came up with[1].\n\nFOOTNOTES\n\n[1] Such as \"Extended LInear Jump AHead\", \"UNabashedly Smart High\nINtelligence Eccentricity\" and \"UNsurprisingly SHallow and\nINsignificant Exception\". Oh wait, don't read this if you don't want\nto be bored.\n"},{"id":"408482","messageId":"CABPp-BEGG7GYH=1dWnC_vxQimB07xPAQHWYcL=yk3rcYOk5SkA@mail.gmail.com","threadId":"54475","inReplyTo":"CAPig+cTkxP=aaCi8YrxbSYptkgwSYG_sd+P6FiXmtA6xuXxGSw@mail.gmail.com","subject":"Re: [PATCH v3 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-27T04:57:06Z","receivedAt":"2020-10-27T04:57:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Oct 26, 2020 at 7:33 PM Eric Sunshine <sunshine@sunshineco.com> wrote:\n>\n> On Mon, Oct 26, 2020 at 10:08 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> > + * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n> > + * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n> > + * to replace\n> > + *\n> > + *   git merge [-s recursive]\n> > + *\n> > + * with\n> > + *\n> > + *   git merge -s ort\n> > + *\n> > + * Note: git's parser allows the space between '-s' and its argument to be\n> > + * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n> > + * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n>\n> You forgot:\n>\n>     git merge -s newren\n>\n> the \"NEW Recursive ENgine\" merge strategy. I won't bore you with some\n> other backronyms I came up with[1].\n>\n> FOOTNOTES\n>\n> [1] Such as \"Extended LInear Jump AHead\", \"UNabashedly Smart High\n> INtelligence Eccentricity\" and \"UNsurprisingly SHallow and\n> INsignificant Exception\". Oh wait, don't read this if you don't want\n> to be bored.\n\nHaha, those are great!  :-)  Maybe we should come up with one for TUFF?\n"},{"id":"408494","messageId":"CAPig+cQ2e4NF6GhRvCTgt1KpPG5Lw_ybPM5FeWtmc69rxvXFuQ@mail.gmail.com","threadId":"54475","inReplyTo":"CABPp-BEGG7GYH=1dWnC_vxQimB07xPAQHWYcL=yk3rcYOk5SkA@mail.gmail.com","subject":"Re: [PATCH v3 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-10-27T07:54:35Z","receivedAt":"2020-10-27T07:54:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 27, 2020 at 12:57 AM Elijah Newren <newren@gmail.com> wrote:\n> On Mon, Oct 26, 2020 at 7:33 PM Eric Sunshine <sunshine@sunshineco.com> wrote:\n> > You forgot:\n> >\n> >     git merge -s newren\n> >\n> > the \"NEW Recursive ENgine\" merge strategy. I won't bore you with some\n> > other backronyms I came up with[1].\n> >\n> > [1] Such as \"Extended LInear Jump AHead\", \"UNabashedly Smart High\n> > INtelligence Eccentricity\" and \"UNsurprisingly SHallow and\n> > INsignificant Exception\". Oh wait, don't read this if you don't want\n> > to be bored.\n>\n> Haha, those are great!  :-)  Maybe we should come up with one for TUFF?\n\nThis \"stuff\" writes itself: Transitive Unified Fundamental Finalizer\n\nActually, I quite like \"Extended LInear Jump AHead\"; it sounds like it\ncould be a genuine algorithm. \"NEW Recursive ENgine\" is also\nreasonably good in that it's a drop-in replacement for the old\n\"recursive\" strategy, although a bit generic as a name and doesn't\ngive an indication of the algorithm like \"Extended LInear Jump AHead\"\ndoes (even if that algorithm is fanciful).\n"},{"id":"408501","messageId":"20201027094722.GG24813@szeder.dev","threadId":"54475","inReplyTo":"27ad7566007676fc69dc28aa3b289547f0ccabca.1603764490.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/4] fast-rebase: demonstrate merge-ort's API via temporary/hidden command","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2020-10-27T09:47:22Z","receivedAt":"2020-10-27T09:47:29Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Oct 27, 2020 at 02:08:09AM +0000, Elijah Newren via GitGitGadget wrote:\n> Add a special built-in that is only of use to git-developers and only\n> during the development of merge-ort, and which is designed to\n> immediately fail and print:\n>    git: 'fast-rebase' is not a git command\n> unless a special GIT_TEST_MERGE_ALGORITHM environment variable is set.\n\n\n> ---\n>  Makefile              |   1 +\n>  builtin.h             |   1 +\n>  builtin/fast-rebase.c | 210 ++++++++++++++++++++++++++++++++++++++++++\n>  git.c                 |   1 +\n>  4 files changed, 213 insertions(+)\n>  create mode 100644 builtin/fast-rebase.c\n\nPlease also list this new command in '.gitignore'.\n\nPerhaps it should be listed in 'command-list.txt' as well, though I'm\nnot sure.  It starts with:\n\n  # All supported commands, builtin or external, must be described in\n  # here. This info is used to list commands in various places.\n\nthough I'm not sure that we want to list such a special-purpose\ncommand in \"various places\", and it appears to work anyway...\n\nOr perhaps this is a sign that this would be better added as a\ntest-tool helper as suggested earlier?\n\n"},{"id":"408660","messageId":"3357ea415e3437966f15bf73fbbeb21cda3df592.1604003535.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","subject":"[PATCH v4 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-29T20:32:11Z","receivedAt":"2020-10-29T20:32:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis is the beginning of a new merge strategy.  While there are some API\ndifferences, and the implementation has some differences in behavior, it\nis essentially meant as an eventual drop-in replacement for\nmerge-recursive.c.  However, it is being built to exist side-by-side\nwith merge-recursive so that we have plenty of time to find out how\nthose differences pan out in the real world while people can still fall\nback to merge-recursive.  (Also, I intend to avoid modifying\nmerge-recursive during this process, to keep it stable.)\n\nThe primary difference noticable here is that the updating of the\nworking tree and index is not done simultaneously with the merge\nalgorithm, but is a separate post-processing step.  The new API is\ndesigned so that one can do repeated merges (e.g. during a rebase or\ncherry-pick) and only update the index and working tree one time at the\nend instead of updating it with every intermediate result.  Also, one\ncan perform a merge between two branches, neither of which match the\nindex or the working tree, without clobbering the index or working tree.\n\nThe next three commits will demonstrate various uses of this new API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile    |  1 +\n merge-ort.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++\n merge-ort.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 111 insertions(+)\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..088770c2ae 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n+LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-recursive.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort.c b/merge-ort.c\nnew file mode 100644\nindex 0000000000..b487901d3e\n--- /dev/null\n+++ b/merge-ort.c\n@@ -0,0 +1,52 @@\n+/*\n+ * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n+ * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n+ * to replace\n+ *\n+ *   git merge [-s recursive]\n+ *\n+ * with\n+ *\n+ *   git merge -s ort\n+ *\n+ * Note: git's parser allows the space between '-s' and its argument to be\n+ * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n+ * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n+ */\n+\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs)\n+{\n+\tdie(\"Not yet implemented\");\n+\tmerge_finalize(opt, result);\n+}\n+\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_incore_nonrecursive(struct merge_options *opt,\n+\t\t\t       struct tree *merge_base,\n+\t\t\t       struct tree *side1,\n+\t\t\t       struct tree *side2,\n+\t\t\t       struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_incore_recursive(struct merge_options *opt,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct commit *side1,\n+\t\t\t    struct commit *side2,\n+\t\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\ndiff --git a/merge-ort.h b/merge-ort.h\nnew file mode 100644\nindex 0000000000..74adccad16\n--- /dev/null\n+++ b/merge-ort.h\n@@ -0,0 +1,58 @@\n+#ifndef MERGE_ORT_H\n+#define MERGE_ORT_H\n+\n+#include \"merge-recursive.h\"\n+\n+struct commit;\n+struct tree;\n+\n+struct merge_result {\n+\t/* Whether the merge is clean */\n+\tint clean;\n+\n+\t/*\n+\t * Result of merge.  If !clean, represents what would go in worktree\n+\t * (thus possibly including files containing conflict markers).\n+\t */\n+\tstruct tree *tree;\n+\n+\t/*\n+\t * Additional metadata used by merge_switch_to_result() or future calls\n+\t * to merge_incore_*().  Includes data needed to update the index (if\n+\t * !clean) and to print \"CONFLICT\" messages.  Not for external use.\n+\t */\n+\tvoid *priv;\n+};\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * working tree and index are untouched.\n+ */\n+void merge_incore_recursive(struct merge_options *opt,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct commit *side1,\n+\t\t\t    struct commit *side2,\n+\t\t\t    struct merge_result *result);\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * working tree and index are untouched.\n+ */\n+void merge_incore_nonrecursive(struct merge_options *opt,\n+\t\t\t       struct tree *merge_base,\n+\t\t\t       struct tree *side1,\n+\t\t\t       struct tree *side2,\n+\t\t\t       struct merge_result *result);\n+\n+/* Update the working tree and index from head to result after incore merge */\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs);\n+\n+/* Do needed cleanup when not calling merge_switch_to_result() */\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408661","messageId":"fce0db8778fd4664e7fc4092882ea13c6a01f39e.1604003535.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","subject":"[PATCH v4 3/4] fast-rebase: demonstrate merge-ort's API via new test-tool command","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-29T20:32:13Z","receivedAt":"2020-10-29T20:32:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd a new test-tool command named 'fast-rebase', which is a\nsuper-slimmed down and nowhere near as capable version of 'git rebase'.\n'test-tool fast-rebase' is not currently planned for usage in the\ntestsuite, but is here for two purposes:\n\n  1) Demonstrate the desired API of merge-ort.  In particular,\n     fast-rebase takes advantage of the separation of the merging\n     operation from the updating of the index and working tree, to\n     allow it to pick N commits, but only update the index and working\n     tree once at the end.  Look for the calls to\n     merge_incore_nonrecursive() and merge_switch_to_result().\n\n  2) Provide a convenient benchmark that isn't polluted by the heavy\n     disk writing and forking of unnecessary processes that comes from\n     sequencer.c and merge-recursive.c.  fast-rebase is not meant to\n     replace sequencer.c, just give ideas on how sequencer.c can be\n     changed.  Updating sequencer.c with these goals is probably a\n     large amount of work; writing a simple targeted command with\n     no documentation, less-than-useful help messages, numerous\n     limitations in terms of flags it can accept and situations it can\n     handle, and which is flagged off from users is a much easier\n     interim step.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile                    |   1 +\n t/helper/test-fast-rebase.c | 211 ++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |   1 +\n t/helper/test-tool.h        |   1 +\n 4 files changed, 214 insertions(+)\n create mode 100644 t/helper/test-fast-rebase.c\n\ndiff --git a/Makefile b/Makefile\nindex 382fe73c76..24b9fecc0f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -704,6 +704,7 @@ TEST_BUILTINS_OBJS += test-dump-fsmonitor.o\n TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n+TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n TEST_BUILTINS_OBJS += test-hash-speed.o\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nnew file mode 100644\nindex 0000000000..373212256a\n--- /dev/null\n+++ b/t/helper/test-fast-rebase.c\n@@ -0,0 +1,211 @@\n+/*\n+ * \"git fast-rebase\" builtin command\n+ *\n+ * FAST: Forking Any Subprocesses (is) Taboo\n+ *\n+ * This is meant SOLELY as a demo of what is possible.  sequencer.c and\n+ * rebase.c should be refactored to use the ideas here, rather than attempting\n+ * to extend this file to replace those (unless Phillip or Dscho say that\n+ * refactoring is too hard and we need a clean slate, but I'm guessing that\n+ * refactoring is the better route).\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"test-tool.h\"\n+\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"lockfile.h\"\n+#include \"merge-ort.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"sequencer.h\"\n+#include \"strvec.h\"\n+#include \"tree.h\"\n+\n+static const char *short_commit_name(struct commit *commit)\n+{\n+\treturn find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);\n+}\n+\n+static struct commit *peel_committish(const char *name)\n+{\n+\tstruct object *obj;\n+\tstruct object_id oid;\n+\n+\tif (get_oid(name, &oid))\n+\t\treturn NULL;\n+\tobj = parse_object(the_repository, &oid);\n+\treturn (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);\n+}\n+\n+static char *get_author(const char *message)\n+{\n+\tsize_t len;\n+\tconst char *a;\n+\n+\ta = find_commit_header(message, \"author\", &len);\n+\tif (a)\n+\t\treturn xmemdupz(a, len);\n+\n+\treturn NULL;\n+}\n+\n+static struct commit *create_commit(struct tree *tree,\n+\t\t\t\t    struct commit *based_on,\n+\t\t\t\t    struct commit *parent)\n+{\n+\tstruct object_id ret;\n+\tstruct object *obj;\n+\tstruct commit_list *parents = NULL;\n+\tchar *author;\n+\tchar *sign_commit = NULL;\n+\tstruct commit_extra_header *extra;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *out_enc = get_commit_output_encoding();\n+\tconst char *message = logmsg_reencode(based_on, NULL, out_enc);\n+\tconst char *orig_message = NULL;\n+\tconst char *exclude_gpgsig[] = { \"gpgsig\", NULL };\n+\n+\tcommit_list_insert(parent, &parents);\n+\textra = read_commit_extra_headers(based_on, exclude_gpgsig);\n+\tfind_commit_subject(message, &orig_message);\n+\tstrbuf_addstr(&msg, orig_message);\n+\tauthor = get_author(message);\n+\treset_ident_date();\n+\tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n+\t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n+\t\terror(_(\"failed to write commit object\"));\n+\t\treturn NULL;\n+\t}\n+\tfree(author);\n+\tstrbuf_release(&msg);\n+\n+\tobj = parse_object(the_repository, &ret);\n+\treturn (struct commit *)obj;\n+}\n+\n+int cmd__fast_rebase(int argc, const char **argv)\n+{\n+\tstruct commit *onto;\n+\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n+\tstruct object_id head;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint clean = 1;\n+\tstruct strvec rev_walk_args = STRVEC_INIT;\n+\tstruct rev_info revs;\n+\tstruct commit *commit;\n+\tstruct merge_options merge_opt;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n+\tstruct merge_result result;\n+\tstruct strbuf reflog_msg = STRBUF_INIT;\n+\tstruct strbuf branch_name = STRBUF_INIT;\n+\n+\t/*\n+\t * test-tool stuff doesn't set up the git directory by default; need to\n+\t * do that manually.\n+\t */\n+\tsetup_git_directory();\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n+\t\tprintf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n+\t\texit(129);\n+\t}\n+\n+\tif (argc != 5 || strcmp(argv[1], \"--onto\"))\n+\t\tdie(\"usage: read the code, figure out how to use it, then do so\");\n+\n+\tonto = peel_committish(argv[2]);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\n+\t/* Sanity check */\n+\tif (get_oid(\"HEAD\", &head))\n+\t\tdie(_(\"Cannot read HEAD\"));\n+\tassert(oideq(&onto->object.oid, &head));\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\tassert(repo_read_index(the_repository) >= 0);\n+\n+\trepo_init_revisions(the_repository, &revs, NULL);\n+\trevs.verbose_header = 1;\n+\trevs.max_parents = 1;\n+\trevs.cherry_mark = 1;\n+\trevs.limited = 1;\n+\trevs.reverse = 1;\n+\trevs.right_only = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[4], \"--not\", argv[3], NULL);\n+\n+\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1)\n+\t\treturn error(_(\"unhandled options\"));\n+\n+\tstrvec_clear(&rev_walk_args);\n+\n+\tif (prepare_revision_walk(&revs) < 0)\n+\t\treturn error(_(\"error preparing revisions\"));\n+\n+\tinit_merge_options(&merge_opt, the_repository);\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_opt.show_rename_progress = 1;\n+\tmerge_opt.branch1 = \"HEAD\";\n+\thead_tree = get_commit_tree(onto);\n+\tresult.tree = head_tree;\n+\tlast_commit = onto;\n+\twhile ((commit = get_revision(&revs))) {\n+\t\tstruct commit *base;\n+\n+\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n+\t\t\toid_to_hex(&commit->object.oid));\n+\t\tassert(commit->parents && !commit->parents->next);\n+\t\tbase = commit->parents->item;\n+\n+\t\tnext_tree = get_commit_tree(commit);\n+\t\tbase_tree = get_commit_tree(base);\n+\n+\t\tmerge_opt.branch2 = short_commit_name(commit);\n+\t\tmerge_opt.ancestor = xstrfmt(\"parent of %s\", merge_opt.branch2);\n+\n+\t\tmerge_incore_nonrecursive(&merge_opt,\n+\t\t\t\t\t  base_tree,\n+\t\t\t\t\t  result.tree,\n+\t\t\t\t\t  next_tree,\n+\t\t\t\t\t  &result);\n+\n+\t\tfree((char*)merge_opt.ancestor);\n+\t\tmerge_opt.ancestor = NULL;\n+\t\tif (!result.clean)\n+\t\t\tdie(\"Aborting: Hit a conflict and restarting is not implemented.\");\n+\t\tlast_picked_commit = commit;\n+\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n+\t}\n+\tfprintf(stderr, \"\\nDone.\\n\");\n+\t/* TODO: There should be some kind of rev_info_free(&revs) call... */\n+\tmemset(&revs, 0, sizeof(revs));\n+\n+\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\n+\tif (result.clean < 0)\n+\t\texit(128);\n+\n+\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n+\t\t    oid_to_hex(&last_picked_commit->object.oid),\n+\t\t    oid_to_hex(&last_commit->object.oid));\n+\tif (update_ref(reflog_msg.buf, branch_name.buf,\n+\t\t       &last_commit->object.oid,\n+\t\t       &last_picked_commit->object.oid,\n+\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n+\t\terror(_(\"could not update %s\"), argv[4]);\n+\t\tdie(\"Failed to update %s\", argv[4]);\n+\t}\n+\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n+\t\tdie(_(\"unable to update HEAD\"));\n+\tstrbuf_release(&reflog_msg);\n+\tstrbuf_release(&branch_name);\n+\n+\tprime_cache_tree(the_repository, the_repository->index, result.tree);\n+\tif (write_locked_index(&the_index, &lock,\n+\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n+\t\tdie(_(\"unable to write %s\"), get_index_file());\n+\treturn (clean == 0);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex a0d3966b29..8bce7db076 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -28,6 +28,7 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-split-index\", cmd__dump_split_index },\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"example-decorate\", cmd__example_decorate },\n+\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\n \t{ \"hashmap\", cmd__hashmap },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 07034d3f38..fd0cafe5ca 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -18,6 +18,7 @@ int cmd__dump_fsmonitor(int argc, const char **argv);\n int cmd__dump_split_index(int argc, const char **argv);\n int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__example_decorate(int argc, const char **argv);\n+int cmd__fast_rebase(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\n int cmd__hashmap(int argc, const char **argv);\n-- \ngitgitgadget\n\n"},{"id":"408662","messageId":"d7f6a834ab4942b3af329eff9c883fcf7affd7f4.1604003535.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","subject":"[PATCH v4 2/4] merge-ort-wrappers: new convience wrappers to mimic the old merge API","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-29T20:32:12Z","receivedAt":"2020-10-29T20:32:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are a few differences between the new API in merge-ort and the old\nAPI in merge-recursive.  While the new API is more flexible, it might\nfeel like more work at times than the old API.  merge-ort-wrappers\ncreates two convenience wrappers taking the exact same arguments as the\nold merge_trees() and merge_recursive() functions and implements them\nvia the new API.  This makes converting existing callsites easier, and\nserves to highlight some of the differences in the API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile             |  1 +\n merge-ort-wrappers.c | 62 ++++++++++++++++++++++++++++++++++++++++++++\n merge-ort-wrappers.h | 25 ++++++++++++++++++\n 3 files changed, 88 insertions(+)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n\ndiff --git a/Makefile b/Makefile\nindex 088770c2ae..382fe73c76 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -922,6 +922,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n 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.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nnew file mode 100644\nindex 0000000000..7eec25f93a\n--- /dev/null\n+++ b/merge-ort-wrappers.c\n@@ -0,0 +1,62 @@\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n+\n+#include \"commit.h\"\n+\n+static int unclean(struct merge_options *opt, struct tree *head)\n+{\n+\t/* Sanity check on repo state; index must match head */\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (head && repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\tfprintf(stderr, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *merge_base)\n+{\n+\tstruct merge_result result;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tif (oideq(&merge_base->object.oid, &merge->object.oid)) {\n+\t\tprintf(_(\"Already up to date!\"));\n+\t\treturn 1;\n+\t}\n+\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_incore_nonrecursive(opt, merge_base, head, merge, &result);\n+\tmerge_switch_to_result(opt, head, &result, 1, 1);\n+\n+\treturn result.clean;\n+}\n+\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *side1,\n+\t\t\tstruct commit *side2,\n+\t\t\tstruct commit_list *merge_bases,\n+\t\t\tstruct commit **result)\n+{\n+\tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\n+\tstruct merge_result tmp;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tmemset(&tmp, 0, sizeof(tmp));\n+\tmerge_incore_recursive(opt, merge_bases, side1, side2, &tmp);\n+\tmerge_switch_to_result(opt, head, &tmp, 1, 1);\n+\t*result = NULL;\n+\n+\treturn tmp.clean;\n+}\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nnew file mode 100644\nindex 0000000000..0c4c57adbb\n--- /dev/null\n+++ b/merge-ort-wrappers.h\n@@ -0,0 +1,25 @@\n+#ifndef MERGE_ORT_WRAPPERS_H\n+#define MERGE_ORT_WRAPPERS_H\n+\n+#include \"merge-recursive.h\"\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * Wrapper mimicking the old merge_trees() function.\n+ */\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *common);\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * Wrapper mimicking the old merge_recursive() function.\n+ */\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *h1,\n+\t\t\tstruct commit *h2,\n+\t\t\tstruct commit_list *ancestors,\n+\t\t\tstruct commit **result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408663","messageId":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v3.git.git.1603764490.gitgitgadget@gmail.com","subject":"[PATCH v4 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-29T20:32:10Z","receivedAt":"2020-10-29T20:32:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In this series, I show the new merge API I have developed in merge-ort, and\nshow how it differs from that provided by merge-recursive. I do this in four\nsteps, each corresponding to a patch.\n\nChanges since v3:\n\n * Make 'fast-rebase' be a test-tool subcommand instead of a special hidden\n   builtin, as suggested by Jonathan Tan and SZEDER Gábor\n\nElijah Newren (4):\n  merge-ort: barebones API of new merge strategy with empty\n    implementation\n  merge-ort-wrappers: new convience wrappers to mimic the old merge API\n  fast-rebase: demonstrate merge-ort's API via new test-tool command\n  merge,rebase,revert: select ort or recursive by config or environment\n\n Makefile                    |   3 +\n builtin/merge.c             |  26 ++++-\n builtin/rebase.c            |   9 +-\n builtin/revert.c            |   2 +\n merge-ort-wrappers.c        |  62 +++++++++++\n merge-ort-wrappers.h        |  25 +++++\n merge-ort.c                 |  52 +++++++++\n merge-ort.h                 |  58 ++++++++++\n sequencer.c                 |  71 ++++++++++--\n t/helper/test-fast-rebase.c | 211 ++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |   1 +\n t/helper/test-tool.h        |   1 +\n 12 files changed, 506 insertions(+), 15 deletions(-)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n create mode 100644 t/helper/test-fast-rebase.c\n\n\nbase-commit: 69986e19ffcfb9af674ae5180689ab7bbf92ed28\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-895%2Fnewren%2Fort-api-with-empty-implementation-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-895/newren/ort-api-with-empty-implementation-v4\nPull-Request: https://github.com/git/git/pull/895\n\nRange-diff vs v3:\n\n 1:  3357ea415e = 1:  3357ea415e merge-ort: barebones API of new merge strategy with empty implementation\n 2:  d7f6a834ab = 2:  d7f6a834ab merge-ort-wrappers: new convience wrappers to mimic the old merge API\n 3:  27ad756600 ! 3:  fce0db8778 fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n     @@ Metadata\n      Author: Elijah Newren <newren@gmail.com>\n      \n       ## Commit message ##\n     -    fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n     +    fast-rebase: demonstrate merge-ort's API via new test-tool command\n      \n     -    Add a special built-in that is only of use to git-developers and only\n     -    during the development of merge-ort, and which is designed to\n     -    immediately fail and print:\n     -       git: 'fast-rebase' is not a git command\n     -    unless a special GIT_TEST_MERGE_ALGORITHM environment variable is set.\n     -\n     -    This special builtin serves two purposes:\n     +    Add a new test-tool command named 'fast-rebase', which is a\n     +    super-slimmed down and nowhere near as capable version of 'git rebase'.\n     +    'test-tool fast-rebase' is not currently planned for usage in the\n     +    testsuite, but is here for two purposes:\n      \n            1) Demonstrate the desired API of merge-ort.  In particular,\n               fast-rebase takes advantage of the separation of the merging\n               operation from the updating of the index and working tree, to\n               allow it to pick N commits, but only update the index and working\n               tree once at the end.  Look for the calls to\n     -         merge_inmemory_nonrecursive() and merge_switch_to_result().\n     +         merge_incore_nonrecursive() and merge_switch_to_result().\n      \n            2) Provide a convenient benchmark that isn't polluted by the heavy\n               disk writing and forking of unnecessary processes that comes from\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Makefile ##\n     -@@ Makefile: BUILTIN_OBJS += builtin/difftool.o\n     - BUILTIN_OBJS += builtin/env--helper.o\n     - BUILTIN_OBJS += builtin/fast-export.o\n     - BUILTIN_OBJS += builtin/fast-import.o\n     -+BUILTIN_OBJS += builtin/fast-rebase.o\n     - BUILTIN_OBJS += builtin/fetch-pack.o\n     - BUILTIN_OBJS += builtin/fetch.o\n     - BUILTIN_OBJS += builtin/fmt-merge-msg.o\n     -\n     - ## builtin.h ##\n     -@@ builtin.h: int cmd_difftool(int argc, const char **argv, const char *prefix);\n     - int cmd_env__helper(int argc, const char **argv, const char *prefix);\n     - int cmd_fast_export(int argc, const char **argv, const char *prefix);\n     - int cmd_fast_import(int argc, const char **argv, const char *prefix);\n     -+int cmd_fast_rebase(int argc, const char **argv, const char *prefix);\n     - int cmd_fetch(int argc, const char **argv, const char *prefix);\n     - int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n     - int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);\n     +@@ Makefile: TEST_BUILTINS_OBJS += test-dump-fsmonitor.o\n     + TEST_BUILTINS_OBJS += test-dump-split-index.o\n     + TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n     + TEST_BUILTINS_OBJS += test-example-decorate.o\n     ++TEST_BUILTINS_OBJS += test-fast-rebase.o\n     + TEST_BUILTINS_OBJS += test-genrandom.o\n     + TEST_BUILTINS_OBJS += test-genzeros.o\n     + TEST_BUILTINS_OBJS += test-hash-speed.o\n      \n     - ## builtin/fast-rebase.c (new) ##\n     + ## t/helper/test-fast-rebase.c (new) ##\n      @@\n      +/*\n      + * \"git fast-rebase\" builtin command\n     @@ builtin/fast-rebase.c (new)\n      + */\n      +\n      +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n     -+#include \"builtin.h\"\n     ++#include \"test-tool.h\"\n      +\n      +#include \"cache-tree.h\"\n      +#include \"commit.h\"\n     @@ builtin/fast-rebase.c (new)\n      +\treturn (struct commit *)obj;\n      +}\n      +\n     -+int cmd_fast_rebase(int argc, const char **argv, const char *prefix)\n     ++int cmd__fast_rebase(int argc, const char **argv)\n      +{\n      +\tstruct commit *onto;\n      +\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n     @@ builtin/fast-rebase.c (new)\n      +\tstruct strbuf reflog_msg = STRBUF_INIT;\n      +\tstruct strbuf branch_name = STRBUF_INIT;\n      +\n     ++\t/*\n     ++\t * test-tool stuff doesn't set up the git directory by default; need to\n     ++\t * do that manually.\n     ++\t */\n     ++\tsetup_git_directory();\n     ++\n      +\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n      +\t\tprintf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n      +\t\texit(129);\n      +\t}\n      +\n     -+\tif (!getenv(\"GIT_TEST_MERGE_ALGORITHM\")) {\n     -+\t\tfprintf_ln(stderr, _(\"git: 'fast-rebase' is not a git command. See 'git --help'.\"));\n     -+\t\texit(1);\n     -+\t}\n     -+\n      +\tif (argc != 5 || strcmp(argv[1], \"--onto\"))\n      +\t\tdie(\"usage: read the code, figure out how to use it, then do so\");\n      +\n     @@ builtin/fast-rebase.c (new)\n      +\treturn (clean == 0);\n      +}\n      \n     - ## git.c ##\n     -@@ git.c: static struct cmd_struct commands[] = {\n     - \t{ \"env--helper\", cmd_env__helper },\n     - \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n     - \t{ \"fast-import\", cmd_fast_import, RUN_SETUP | NO_PARSEOPT },\n     -+\t{ \"fast-rebase\", cmd_fast_rebase, RUN_SETUP /* | NEED_WORK_TREE */ },\n     - \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n     - \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP | NO_PARSEOPT },\n     - \t{ \"fmt-merge-msg\", cmd_fmt_merge_msg, RUN_SETUP },\n     + ## t/helper/test-tool.c ##\n     +@@ t/helper/test-tool.c: static struct test_cmd cmds[] = {\n     + \t{ \"dump-split-index\", cmd__dump_split_index },\n     + \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n     + \t{ \"example-decorate\", cmd__example_decorate },\n     ++\t{ \"fast-rebase\", cmd__fast_rebase },\n     + \t{ \"genrandom\", cmd__genrandom },\n     + \t{ \"genzeros\", cmd__genzeros },\n     + \t{ \"hashmap\", cmd__hashmap },\n     +\n     + ## t/helper/test-tool.h ##\n     +@@ t/helper/test-tool.h: int cmd__dump_fsmonitor(int argc, const char **argv);\n     + int cmd__dump_split_index(int argc, const char **argv);\n     + int cmd__dump_untracked_cache(int argc, const char **argv);\n     + int cmd__example_decorate(int argc, const char **argv);\n     ++int cmd__fast_rebase(int argc, const char **argv);\n     + int cmd__genrandom(int argc, const char **argv);\n     + int cmd__genzeros(int argc, const char **argv);\n     + int cmd__hashmap(int argc, const char **argv);\n 4:  0479d59c33 = 4:  75d19804bd merge,rebase,revert: select ort or recursive by config or environment\n\n-- \ngitgitgadget\n"},{"id":"408664","messageId":"75d19804bd1dbf02c3961d5c41b041879d6ed9bf.1604003535.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","subject":"[PATCH v4 4/4] merge,rebase,revert: select ort or recursive by config or environment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-29T20:32:14Z","receivedAt":"2020-10-29T20:32:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAllow the testsuite to run where it treats requests for \"recursive\" or\nthe default merge algorithm via consulting the environment variable\nGIT_TEST_MERGE_ALGORITHM which is expected to either be \"recursive\" (the\nold traditional algorithm) or \"ort\" (the new algorithm).\n\nAlso, allow folks to pick the new algorithm via config setting.  It\nturns out builtin/merge.c already had a way to allow users to specify a\ndifferent default merge algorithm: pull.twohead.  Rather odd\nconfiguration name (especially to be in the 'pull' namespace rather than\n'merge') but it's there.  Add that same configuration to rebase,\ncherry-pick, and revert.\n\nThis required updating the various callsites that called merge_trees()\nor merge_recursive() to conditionally call the new API, so this serves\nas another demonstration of what the new API looks and feels like.\nThere are almost certainly some callsites that have not yet been\nmodified to work with the new merge algorithm, but this represents the\nones that I have been testing with thus far.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge.c  | 26 ++++++++++++++++--\n builtin/rebase.c |  9 +++++-\n builtin/revert.c |  2 ++\n sequencer.c      | 71 ++++++++++++++++++++++++++++++++++++++++--------\n 4 files changed, 93 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..87dfc9bc06 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -28,6 +28,7 @@\n #include \"rerere.h\"\n #include \"help.h\"\n #include \"merge-recursive.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n #include \"fmt-merge-msg.h\"\n@@ -88,6 +89,7 @@ static int no_verify;\n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n \t{ \"octopus\",    DEFAULT_OCTOPUS },\n+\t{ \"ort\",        NO_TRIVIAL },\n \t{ \"resolve\",    0 },\n \t{ \"ours\",       NO_FAST_FORWARD | NO_TRIVIAL },\n \t{ \"subtree\",    NO_FAST_FORWARD | NO_TRIVIAL },\n@@ -159,10 +161,17 @@ static struct strategy *get_strategy(const char *name)\n \tstruct strategy *ret;\n \tstatic struct cmdnames main_cmds, other_cmds;\n \tstatic int loaded;\n+\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n \tif (!name)\n \t\treturn NULL;\n \n+\tif (default_strategy &&\n+\t    !strcmp(default_strategy, \"ort\") &&\n+\t    !strcmp(name, \"recursive\")) {\n+\t\tname = \"ort\";\n+\t}\n+\n \tfor (i = 0; i < ARRAY_SIZE(all_strategy); i++)\n \t\tif (!strcmp(name, all_strategy[i].name))\n \t\t\treturn &all_strategy[i];\n@@ -701,7 +710,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)\n \t\treturn error(_(\"Unable to write index.\"));\n \n-\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\")) {\n+\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n+\t    !strcmp(strategy, \"ort\")) {\n \t\tstruct lock_file lock = LOCK_INIT;\n \t\tint clean, x;\n \t\tstruct commit *result;\n@@ -732,8 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\tcommit_list_insert(j->item, &reversed);\n \n \t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n-\t\tclean = merge_recursive(&o, head,\n-\t\t\t\tremoteheads->item, reversed, &result);\n+\t\tif (!strcmp(strategy, \"ort\"))\n+\t\t\tclean = merge_ort_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\t    reversed, &result);\n+\t\telse\n+\t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\treversed, &result);\n \t\tif (clean < 0)\n \t\t\texit(128);\n \t\tif (write_locked_index(&the_index, &lock,\n@@ -1264,6 +1278,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (branch)\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n \n+\tif (!pull_twohead) {\n+\t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n+\t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n+\t\t\tpull_twohead = \"ort\";\n+\t}\n+\n \tinit_diff_ui_defaults();\n \tgit_config(git_merge_config, NULL);\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex eeca53382f..9719aa25da 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -119,6 +119,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \treplay.action = REPLAY_INTERACTIVE_REBASE;\n+\treplay.strategy = NULL;\n \tsequencer_init_config(&replay);\n \n \treplay.signoff = opts->signoff;\n@@ -136,7 +137,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n-\treplay.strategy = opts->strategy;\n+\tif (opts->strategy)\n+\t\treplay.strategy = opts->strategy;\n \n \tif (opts->strategy_opts)\n \t\tparse_strategy_opts(&replay, opts->strategy_opts);\n@@ -1771,6 +1773,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t    options.default_backend);\n \t}\n \n+\tif (options.type == REBASE_MERGE &&\n+\t    !options.strategy &&\n+\t    getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\toptions.strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_PRESERVE_MERGES:\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex f61cc5d82c..c7cb0c1a18 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -202,6 +202,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t/* These option values will be free()d */\n \topts->gpg_sign = xstrdup_or_null(opts->gpg_sign);\n \topts->strategy = xstrdup_or_null(opts->strategy);\n+\tif (!opts->strategy && getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\topts->strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n \n \tif (cmd == 'q') {\n \t\tint ret = sequencer_remove_state(opts);\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..e16d85380c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -14,7 +14,8 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n-#include \"merge-recursive.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"refs.h\"\n #include \"strvec.h\"\n #include \"quote.h\"\n@@ -204,6 +205,20 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (!opts->strategy && !strcmp(k, \"pull.twohead\")) {\n+\t\tint ret = git_config_string((const char**)&opts->strategy, k, v);\n+\t\tif (ret == 0) {\n+\t\t\t/*\n+\t\t\t * pull.twohead is allowed to be multi-valued; we only\n+\t\t\t * care about the first value.\n+\t\t\t */\n+\t\t\tchar *tmp = strchr(opts->strategy, ' ');\n+\t\t\tif (tmp)\n+\t\t\t\t*tmp = '\\0';\n+\t\t}\n+\t\treturn ret;\n+\t}\n+\n \tstatus = git_gpg_config(k, v, NULL);\n \tif (status)\n \t\treturn status;\n@@ -595,8 +610,9 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n+\tstruct merge_result result;\n \tstruct tree *next_tree, *base_tree, *head_tree;\n-\tint clean;\n+\tint clean, show_output;\n \tint i;\n \tstruct lock_file index_lock = LOCK_INIT;\n \n@@ -620,12 +636,27 @@ static int do_recursive_merge(struct repository *r,\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tparse_merge_opt(&o, opts->xopts[i]);\n \n-\tclean = merge_trees(&o,\n-\t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree);\n-\tif (is_rebase_i(opts) && clean <= 0)\n-\t\tfputs(o.obuf.buf, stdout);\n-\tstrbuf_release(&o.obuf);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\tmemset(&result, 0, sizeof(result));\n+\t\tmerge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,\n+\t\t\t\t\t    &result);\n+\t\tshow_output = !is_rebase_i(opts) || !result.clean;\n+\t\t/*\n+\t\t * TODO: merge_switch_to_result will update index/working tree;\n+\t\t * we only really want to do that if !result.clean || this is\n+\t\t * the final patch to be picked.  But determining this is the\n+\t\t * final patch would take some work, and \"head_tree\" would need\n+\t\t * to be replace with the tree the index matched before we\n+\t\t * started doing any picks.\n+\t\t */\n+\t\tmerge_switch_to_result(&o, head_tree, &result, 1, show_output);\n+\t\tclean = result.clean;\n+\t} else {\n+\t\tclean = merge_trees(&o, head_tree, next_tree, base_tree);\n+\t\tif (is_rebase_i(opts) && clean <= 0)\n+\t\t\tfputs(o.obuf.buf, stdout);\n+\t\tstrbuf_release(&o.obuf);\n+\t}\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n@@ -1991,7 +2022,10 @@ static int do_pick_commit(struct repository *r,\n \n \tif (is_rebase_i(opts) && write_author_script(msg.message) < 0)\n \t\tres = -1;\n-\telse if (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || command == TODO_REVERT) {\n+\telse if (!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\") ||\n+\t\t command == TODO_REVERT) {\n \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n \t\t\t\t\t &head, &msgbuf, opts);\n \t\tif (res < 0)\n@@ -3485,7 +3519,9 @@ static int do_merge(struct repository *r,\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts_nr &&\n-\t\t(!opts->strategy || !strcmp(opts->strategy, \"recursive\")) ?\n+\t\t(!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\")) ?\n \t\tNULL : opts->strategy;\n \tstruct merge_options o;\n \tint merge_arg_len, oneline_offset, can_fast_forward, ret, k;\n@@ -3722,7 +3758,20 @@ static int do_merge(struct repository *r,\n \to.branch2 = ref_name.buf;\n \to.buffer_output = 2;\n \n-\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\t/*\n+\t\t * TODO: Should use merge_incore_recursive() and\n+\t\t * merge_switch_to_result(), skipping the call to\n+\t\t * merge_switch_to_result() when we don't actually need to\n+\t\t * update the index and working copy immediately.\n+\t\t */\n+\t\tret = merge_ort_recursive(&o,\n+\t\t\t\t\t  head_commit, merge_commit, reversed,\n+\t\t\t\t\t  &i);\n+\t} else {\n+\t\tret = merge_recursive(&o, head_commit, merge_commit, reversed,\n+\t\t\t\t      &i);\n+\t}\n \tif (ret <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\n-- \ngitgitgadget\n"},{"id":"408854","messageId":"CA+P7+xqFrzT53vx70tRDFgZXM3i2FJJK9CpuJJfkfsN4Qj6ChQ@mail.gmail.com","threadId":"54475","inReplyTo":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Jacob Keller","fromEmail":"jacob.keller@gmail.com","sentAt":"2020-11-02T09:27:59Z","receivedAt":"2020-11-02T09:28:12Z","isPatch":true,"sender":{"key":"jacob.keller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/874719?v=4"},"body":"On Thu, Oct 29, 2020 at 1:34 PM Elijah Newren via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> In this series, I show the new merge API I have developed in merge-ort, and\n> show how it differs from that provided by merge-recursive. I do this in four\n> steps, each corresponding to a patch.\n>\n\nI'm definitely excited by this project. I'm curious if you have any\nfurther implementation as a WIP that could be played with to see the\nend result of the new merging?\n\nI definitely like this approach where you work in smaller increments\nto make the implementation easier to review!\n\nThanks,\nJake\n\n> Changes since v3:\n>\n>  * Make 'fast-rebase' be a test-tool subcommand instead of a special hidden\n>    builtin, as suggested by Jonathan Tan and SZEDER Gábor\n>\n> Elijah Newren (4):\n>   merge-ort: barebones API of new merge strategy with empty\n>     implementation\n>   merge-ort-wrappers: new convience wrappers to mimic the old merge API\n>   fast-rebase: demonstrate merge-ort's API via new test-tool command\n>   merge,rebase,revert: select ort or recursive by config or environment\n>\n>  Makefile                    |   3 +\n>  builtin/merge.c             |  26 ++++-\n>  builtin/rebase.c            |   9 +-\n>  builtin/revert.c            |   2 +\n>  merge-ort-wrappers.c        |  62 +++++++++++\n>  merge-ort-wrappers.h        |  25 +++++\n>  merge-ort.c                 |  52 +++++++++\n>  merge-ort.h                 |  58 ++++++++++\n>  sequencer.c                 |  71 ++++++++++--\n>  t/helper/test-fast-rebase.c | 211 ++++++++++++++++++++++++++++++++++++\n>  t/helper/test-tool.c        |   1 +\n>  t/helper/test-tool.h        |   1 +\n>  12 files changed, 506 insertions(+), 15 deletions(-)\n>  create mode 100644 merge-ort-wrappers.c\n>  create mode 100644 merge-ort-wrappers.h\n>  create mode 100644 merge-ort.c\n>  create mode 100644 merge-ort.h\n>  create mode 100644 t/helper/test-fast-rebase.c\n>\n>\n> base-commit: 69986e19ffcfb9af674ae5180689ab7bbf92ed28\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-895%2Fnewren%2Fort-api-with-empty-implementation-v4\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-895/newren/ort-api-with-empty-implementation-v4\n> Pull-Request: https://github.com/git/git/pull/895\n>\n> Range-diff vs v3:\n>\n>  1:  3357ea415e = 1:  3357ea415e merge-ort: barebones API of new merge strategy with empty implementation\n>  2:  d7f6a834ab = 2:  d7f6a834ab merge-ort-wrappers: new convience wrappers to mimic the old merge API\n>  3:  27ad756600 ! 3:  fce0db8778 fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n>      @@ Metadata\n>       Author: Elijah Newren <newren@gmail.com>\n>\n>        ## Commit message ##\n>      -    fast-rebase: demonstrate merge-ort's API via temporary/hidden command\n>      +    fast-rebase: demonstrate merge-ort's API via new test-tool command\n>\n>      -    Add a special built-in that is only of use to git-developers and only\n>      -    during the development of merge-ort, and which is designed to\n>      -    immediately fail and print:\n>      -       git: 'fast-rebase' is not a git command\n>      -    unless a special GIT_TEST_MERGE_ALGORITHM environment variable is set.\n>      -\n>      -    This special builtin serves two purposes:\n>      +    Add a new test-tool command named 'fast-rebase', which is a\n>      +    super-slimmed down and nowhere near as capable version of 'git rebase'.\n>      +    'test-tool fast-rebase' is not currently planned for usage in the\n>      +    testsuite, but is here for two purposes:\n>\n>             1) Demonstrate the desired API of merge-ort.  In particular,\n>                fast-rebase takes advantage of the separation of the merging\n>                operation from the updating of the index and working tree, to\n>                allow it to pick N commits, but only update the index and working\n>                tree once at the end.  Look for the calls to\n>      -         merge_inmemory_nonrecursive() and merge_switch_to_result().\n>      +         merge_incore_nonrecursive() and merge_switch_to_result().\n>\n>             2) Provide a convenient benchmark that isn't polluted by the heavy\n>                disk writing and forking of unnecessary processes that comes from\n>      @@ Commit message\n>           Signed-off-by: Elijah Newren <newren@gmail.com>\n>\n>        ## Makefile ##\n>      -@@ Makefile: BUILTIN_OBJS += builtin/difftool.o\n>      - BUILTIN_OBJS += builtin/env--helper.o\n>      - BUILTIN_OBJS += builtin/fast-export.o\n>      - BUILTIN_OBJS += builtin/fast-import.o\n>      -+BUILTIN_OBJS += builtin/fast-rebase.o\n>      - BUILTIN_OBJS += builtin/fetch-pack.o\n>      - BUILTIN_OBJS += builtin/fetch.o\n>      - BUILTIN_OBJS += builtin/fmt-merge-msg.o\n>      -\n>      - ## builtin.h ##\n>      -@@ builtin.h: int cmd_difftool(int argc, const char **argv, const char *prefix);\n>      - int cmd_env__helper(int argc, const char **argv, const char *prefix);\n>      - int cmd_fast_export(int argc, const char **argv, const char *prefix);\n>      - int cmd_fast_import(int argc, const char **argv, const char *prefix);\n>      -+int cmd_fast_rebase(int argc, const char **argv, const char *prefix);\n>      - int cmd_fetch(int argc, const char **argv, const char *prefix);\n>      - int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\n>      - int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);\n>      +@@ Makefile: TEST_BUILTINS_OBJS += test-dump-fsmonitor.o\n>      + TEST_BUILTINS_OBJS += test-dump-split-index.o\n>      + TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n>      + TEST_BUILTINS_OBJS += test-example-decorate.o\n>      ++TEST_BUILTINS_OBJS += test-fast-rebase.o\n>      + TEST_BUILTINS_OBJS += test-genrandom.o\n>      + TEST_BUILTINS_OBJS += test-genzeros.o\n>      + TEST_BUILTINS_OBJS += test-hash-speed.o\n>\n>      - ## builtin/fast-rebase.c (new) ##\n>      + ## t/helper/test-fast-rebase.c (new) ##\n>       @@\n>       +/*\n>       + * \"git fast-rebase\" builtin command\n>      @@ builtin/fast-rebase.c (new)\n>       + */\n>       +\n>       +#define USE_THE_INDEX_COMPATIBILITY_MACROS\n>      -+#include \"builtin.h\"\n>      ++#include \"test-tool.h\"\n>       +\n>       +#include \"cache-tree.h\"\n>       +#include \"commit.h\"\n>      @@ builtin/fast-rebase.c (new)\n>       + return (struct commit *)obj;\n>       +}\n>       +\n>      -+int cmd_fast_rebase(int argc, const char **argv, const char *prefix)\n>      ++int cmd__fast_rebase(int argc, const char **argv)\n>       +{\n>       + struct commit *onto;\n>       + struct commit *last_commit = NULL, *last_picked_commit = NULL;\n>      @@ builtin/fast-rebase.c (new)\n>       + struct strbuf reflog_msg = STRBUF_INIT;\n>       + struct strbuf branch_name = STRBUF_INIT;\n>       +\n>      ++ /*\n>      ++  * test-tool stuff doesn't set up the git directory by default; need to\n>      ++  * do that manually.\n>      ++  */\n>      ++ setup_git_directory();\n>      ++\n>       + if (argc == 2 && !strcmp(argv[1], \"-h\")) {\n>       +         printf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n>       +         exit(129);\n>       + }\n>       +\n>      -+ if (!getenv(\"GIT_TEST_MERGE_ALGORITHM\")) {\n>      -+         fprintf_ln(stderr, _(\"git: 'fast-rebase' is not a git command. See 'git --help'.\"));\n>      -+         exit(1);\n>      -+ }\n>      -+\n>       + if (argc != 5 || strcmp(argv[1], \"--onto\"))\n>       +         die(\"usage: read the code, figure out how to use it, then do so\");\n>       +\n>      @@ builtin/fast-rebase.c (new)\n>       + return (clean == 0);\n>       +}\n>\n>      - ## git.c ##\n>      -@@ git.c: static struct cmd_struct commands[] = {\n>      -  { \"env--helper\", cmd_env__helper },\n>      -  { \"fast-export\", cmd_fast_export, RUN_SETUP },\n>      -  { \"fast-import\", cmd_fast_import, RUN_SETUP | NO_PARSEOPT },\n>      -+ { \"fast-rebase\", cmd_fast_rebase, RUN_SETUP /* | NEED_WORK_TREE */ },\n>      -  { \"fetch\", cmd_fetch, RUN_SETUP },\n>      -  { \"fetch-pack\", cmd_fetch_pack, RUN_SETUP | NO_PARSEOPT },\n>      -  { \"fmt-merge-msg\", cmd_fmt_merge_msg, RUN_SETUP },\n>      + ## t/helper/test-tool.c ##\n>      +@@ t/helper/test-tool.c: static struct test_cmd cmds[] = {\n>      +  { \"dump-split-index\", cmd__dump_split_index },\n>      +  { \"dump-untracked-cache\", cmd__dump_untracked_cache },\n>      +  { \"example-decorate\", cmd__example_decorate },\n>      ++ { \"fast-rebase\", cmd__fast_rebase },\n>      +  { \"genrandom\", cmd__genrandom },\n>      +  { \"genzeros\", cmd__genzeros },\n>      +  { \"hashmap\", cmd__hashmap },\n>      +\n>      + ## t/helper/test-tool.h ##\n>      +@@ t/helper/test-tool.h: int cmd__dump_fsmonitor(int argc, const char **argv);\n>      + int cmd__dump_split_index(int argc, const char **argv);\n>      + int cmd__dump_untracked_cache(int argc, const char **argv);\n>      + int cmd__example_decorate(int argc, const char **argv);\n>      ++int cmd__fast_rebase(int argc, const char **argv);\n>      + int cmd__genrandom(int argc, const char **argv);\n>      + int cmd__genzeros(int argc, const char **argv);\n>      + int cmd__hashmap(int argc, const char **argv);\n>  4:  0479d59c33 = 4:  75d19804bd merge,rebase,revert: select ort or recursive by config or environment\n>\n> --\n> gitgitgadget\n"},{"id":"408878","messageId":"CABPp-BGELtrMa4TK-dkKWxrQ_iQHyEo1yaJBGC_ZyFWG7-9gKg@mail.gmail.com","threadId":"54475","inReplyTo":"CA+P7+xqFrzT53vx70tRDFgZXM3i2FJJK9CpuJJfkfsN4Qj6ChQ@mail.gmail.com","subject":"Re: [PATCH v4 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-02T18:52:23Z","receivedAt":"2020-11-02T18:52:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Nov 2, 2020 at 1:28 AM Jacob Keller <jacob.keller@gmail.com> wrote:\n>\n> On Thu, Oct 29, 2020 at 1:34 PM Elijah Newren via GitGitGadget\n> <gitgitgadget@gmail.com> wrote:\n> >\n> > In this series, I show the new merge API I have developed in merge-ort, and\n> > show how it differs from that provided by merge-recursive. I do this in four\n> > steps, each corresponding to a patch.\n> >\n>\n> I'm definitely excited by this project. I'm curious if you have any\n> further implementation as a WIP that could be played with to see the\n> end result of the new merging?\n>\n> I definitely like this approach where you work in smaller increments\n> to make the implementation easier to review!\n\nI usually keep the 'ort' branch of https://github.com/newren/git\nfunctional (no promises, though).  It has lots of ifdefs, super ugly\ncommits, todos & fixmes, and random additional (non-code) files where\nI was tracking various things I was working on, so the code and the\ntree may not be super readable, but it should be usable (and passes\nall the tests) -- just set pull.twohead=ort in your git config, or set\nthe environment variable GIT_TEST_MERGE_ALGORITHM=ort.\n\nOne warning: git cherry-pick --continue fails with \"Cannot specify\nboth --continue and --strategy\"; my handling to set a --strategy\noption when pull.twohead was set apparently needs some tweaks.  If you\nspot any bugs or other issues, let me know.\n"},{"id":"408952","messageId":"pull.895.v5.git.git.1604360734.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v4.git.git.1604003535.gitgitgadget@gmail.com","subject":"[PATCH v5 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T23:45:30Z","receivedAt":"2020-11-02T23:45:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In this series, I show the new merge API I have developed in merge-ort, and\nshow how it differs from that provided by merge-recursive. I do this in four\nsteps, each corresponding to a patch.\n\nChanges since v4:\n\n * Fix a bug where 'cherry-pick --continue' would report 'fatal:\n   cherry-pick: --strategy cannot be used with --continue' when pull.twohead\n   was set to ort (found by user of internal deployment at $DAYJOB)\n\nElijah Newren (4):\n  merge-ort: barebones API of new merge strategy with empty\n    implementation\n  merge-ort-wrappers: new convience wrappers to mimic the old merge API\n  fast-rebase: demonstrate merge-ort's API via new test-tool command\n  merge,rebase,revert: select ort or recursive by config or environment\n\n Makefile                    |   3 +\n builtin/merge.c             |  26 ++++-\n builtin/rebase.c            |  13 ++-\n builtin/revert.c            |   7 ++\n merge-ort-wrappers.c        |  62 +++++++++++\n merge-ort-wrappers.h        |  25 +++++\n merge-ort.c                 |  52 +++++++++\n merge-ort.h                 |  58 ++++++++++\n sequencer.c                 |  72 ++++++++++--\n sequencer.h                 |   1 +\n t/helper/test-fast-rebase.c | 211 ++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |   1 +\n t/helper/test-tool.h        |   1 +\n 13 files changed, 517 insertions(+), 15 deletions(-)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n create mode 100644 t/helper/test-fast-rebase.c\n\n\nbase-commit: 69986e19ffcfb9af674ae5180689ab7bbf92ed28\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-895%2Fnewren%2Fort-api-with-empty-implementation-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-895/newren/ort-api-with-empty-implementation-v5\nPull-Request: https://github.com/git/git/pull/895\n\nRange-diff vs v4:\n\n 1:  3357ea415e = 1:  3357ea415e merge-ort: barebones API of new merge strategy with empty implementation\n 2:  d7f6a834ab = 2:  d7f6a834ab merge-ort-wrappers: new convience wrappers to mimic the old merge API\n 3:  fce0db8778 = 3:  fce0db8778 fast-rebase: demonstrate merge-ort's API via new test-tool command\n 4:  75d19804bd ! 4:  61217a83bd merge,rebase,revert: select ort or recursive by config or environment\n     @@ builtin/rebase.c: static struct replay_opts get_replay_opts(const struct rebase_\n      -\treplay.strategy = opts->strategy;\n      +\tif (opts->strategy)\n      +\t\treplay.strategy = opts->strategy;\n     ++\telse if (!replay.strategy && replay.default_strategy) {\n     ++\t\treplay.strategy = replay.default_strategy;\n     ++\t\treplay.default_strategy = NULL;\n     ++\t}\n       \n       \tif (opts->strategy_opts)\n       \t\tparse_strategy_opts(&replay, opts->strategy_opts);\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n       \tcase REBASE_PRESERVE_MERGES:\n      \n       ## builtin/revert.c ##\n     +@@ builtin/revert.c: static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n     + \t\t\t\tNULL);\n     + \t}\n     + \n     ++\tif (!opts->strategy && opts->default_strategy) {\n     ++\t\topts->strategy = opts->default_strategy;\n     ++\t\topts->default_strategy = NULL;\n     ++\t}\n     ++\n     + \tif (opts->allow_ff)\n     + \t\tverify_opt_compatible(me, \"--ff\",\n     + \t\t\t\t\"--signoff\", opts->signoff,\n      @@ builtin/revert.c: static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n       \t/* These option values will be free()d */\n       \topts->gpg_sign = xstrdup_or_null(opts->gpg_sign);\n     @@ sequencer.c: static int git_sequencer_config(const char *k, const char *v, void\n       \t\treturn 0;\n       \t}\n       \n     -+\tif (!opts->strategy && !strcmp(k, \"pull.twohead\")) {\n     -+\t\tint ret = git_config_string((const char**)&opts->strategy, k, v);\n     ++\tif (!opts->default_strategy && !strcmp(k, \"pull.twohead\")) {\n     ++\t\tint ret = git_config_string((const char**)&opts->default_strategy, k, v);\n      +\t\tif (ret == 0) {\n      +\t\t\t/*\n      +\t\t\t * pull.twohead is allowed to be multi-valued; we only\n      +\t\t\t * care about the first value.\n      +\t\t\t */\n     -+\t\t\tchar *tmp = strchr(opts->strategy, ' ');\n     ++\t\t\tchar *tmp = strchr(opts->default_strategy, ' ');\n      +\t\t\tif (tmp)\n      +\t\t\t\t*tmp = '\\0';\n      +\t\t}\n     @@ sequencer.c: static int git_sequencer_config(const char *k, const char *v, void\n       \tstatus = git_gpg_config(k, v, NULL);\n       \tif (status)\n       \t\treturn status;\n     +@@ sequencer.c: int sequencer_remove_state(struct replay_opts *opts)\n     + \tfree(opts->committer_name);\n     + \tfree(opts->committer_email);\n     + \tfree(opts->gpg_sign);\n     ++\tfree(opts->default_strategy);\n     + \tfree(opts->strategy);\n     + \tfor (i = 0; i < opts->xopts_nr; i++)\n     + \t\tfree(opts->xopts[i]);\n      @@ sequencer.c: static int do_recursive_merge(struct repository *r,\n       \t\t\t      struct replay_opts *opts)\n       {\n     @@ sequencer.c: static int do_merge(struct repository *r,\n       \tif (ret <= 0)\n       \t\tfputs(o.obuf.buf, stdout);\n       \tstrbuf_release(&o.obuf);\n     +\n     + ## sequencer.h ##\n     +@@ sequencer.h: struct replay_opts {\n     + \tint explicit_cleanup;\n     + \n     + \t/* Merge strategy */\n     ++\tchar *default_strategy;  /* from config options */\n     + \tchar *strategy;\n     + \tchar **xopts;\n     + \tsize_t xopts_nr, xopts_alloc;\n\n-- \ngitgitgadget\n"},{"id":"408953","messageId":"fce0db8778fd4664e7fc4092882ea13c6a01f39e.1604360734.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v5.git.git.1604360734.gitgitgadget@gmail.com","subject":"[PATCH v5 3/4] fast-rebase: demonstrate merge-ort's API via new test-tool command","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T23:45:33Z","receivedAt":"2020-11-02T23:45:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd a new test-tool command named 'fast-rebase', which is a\nsuper-slimmed down and nowhere near as capable version of 'git rebase'.\n'test-tool fast-rebase' is not currently planned for usage in the\ntestsuite, but is here for two purposes:\n\n  1) Demonstrate the desired API of merge-ort.  In particular,\n     fast-rebase takes advantage of the separation of the merging\n     operation from the updating of the index and working tree, to\n     allow it to pick N commits, but only update the index and working\n     tree once at the end.  Look for the calls to\n     merge_incore_nonrecursive() and merge_switch_to_result().\n\n  2) Provide a convenient benchmark that isn't polluted by the heavy\n     disk writing and forking of unnecessary processes that comes from\n     sequencer.c and merge-recursive.c.  fast-rebase is not meant to\n     replace sequencer.c, just give ideas on how sequencer.c can be\n     changed.  Updating sequencer.c with these goals is probably a\n     large amount of work; writing a simple targeted command with\n     no documentation, less-than-useful help messages, numerous\n     limitations in terms of flags it can accept and situations it can\n     handle, and which is flagged off from users is a much easier\n     interim step.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile                    |   1 +\n t/helper/test-fast-rebase.c | 211 ++++++++++++++++++++++++++++++++++++\n t/helper/test-tool.c        |   1 +\n t/helper/test-tool.h        |   1 +\n 4 files changed, 214 insertions(+)\n create mode 100644 t/helper/test-fast-rebase.c\n\ndiff --git a/Makefile b/Makefile\nindex 382fe73c76..24b9fecc0f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -704,6 +704,7 @@ TEST_BUILTINS_OBJS += test-dump-fsmonitor.o\n TEST_BUILTINS_OBJS += test-dump-split-index.o\n TEST_BUILTINS_OBJS += test-dump-untracked-cache.o\n TEST_BUILTINS_OBJS += test-example-decorate.o\n+TEST_BUILTINS_OBJS += test-fast-rebase.o\n TEST_BUILTINS_OBJS += test-genrandom.o\n TEST_BUILTINS_OBJS += test-genzeros.o\n TEST_BUILTINS_OBJS += test-hash-speed.o\ndiff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c\nnew file mode 100644\nindex 0000000000..373212256a\n--- /dev/null\n+++ b/t/helper/test-fast-rebase.c\n@@ -0,0 +1,211 @@\n+/*\n+ * \"git fast-rebase\" builtin command\n+ *\n+ * FAST: Forking Any Subprocesses (is) Taboo\n+ *\n+ * This is meant SOLELY as a demo of what is possible.  sequencer.c and\n+ * rebase.c should be refactored to use the ideas here, rather than attempting\n+ * to extend this file to replace those (unless Phillip or Dscho say that\n+ * refactoring is too hard and we need a clean slate, but I'm guessing that\n+ * refactoring is the better route).\n+ */\n+\n+#define USE_THE_INDEX_COMPATIBILITY_MACROS\n+#include \"test-tool.h\"\n+\n+#include \"cache-tree.h\"\n+#include \"commit.h\"\n+#include \"lockfile.h\"\n+#include \"merge-ort.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+#include \"sequencer.h\"\n+#include \"strvec.h\"\n+#include \"tree.h\"\n+\n+static const char *short_commit_name(struct commit *commit)\n+{\n+\treturn find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);\n+}\n+\n+static struct commit *peel_committish(const char *name)\n+{\n+\tstruct object *obj;\n+\tstruct object_id oid;\n+\n+\tif (get_oid(name, &oid))\n+\t\treturn NULL;\n+\tobj = parse_object(the_repository, &oid);\n+\treturn (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);\n+}\n+\n+static char *get_author(const char *message)\n+{\n+\tsize_t len;\n+\tconst char *a;\n+\n+\ta = find_commit_header(message, \"author\", &len);\n+\tif (a)\n+\t\treturn xmemdupz(a, len);\n+\n+\treturn NULL;\n+}\n+\n+static struct commit *create_commit(struct tree *tree,\n+\t\t\t\t    struct commit *based_on,\n+\t\t\t\t    struct commit *parent)\n+{\n+\tstruct object_id ret;\n+\tstruct object *obj;\n+\tstruct commit_list *parents = NULL;\n+\tchar *author;\n+\tchar *sign_commit = NULL;\n+\tstruct commit_extra_header *extra;\n+\tstruct strbuf msg = STRBUF_INIT;\n+\tconst char *out_enc = get_commit_output_encoding();\n+\tconst char *message = logmsg_reencode(based_on, NULL, out_enc);\n+\tconst char *orig_message = NULL;\n+\tconst char *exclude_gpgsig[] = { \"gpgsig\", NULL };\n+\n+\tcommit_list_insert(parent, &parents);\n+\textra = read_commit_extra_headers(based_on, exclude_gpgsig);\n+\tfind_commit_subject(message, &orig_message);\n+\tstrbuf_addstr(&msg, orig_message);\n+\tauthor = get_author(message);\n+\treset_ident_date();\n+\tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n+\t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n+\t\terror(_(\"failed to write commit object\"));\n+\t\treturn NULL;\n+\t}\n+\tfree(author);\n+\tstrbuf_release(&msg);\n+\n+\tobj = parse_object(the_repository, &ret);\n+\treturn (struct commit *)obj;\n+}\n+\n+int cmd__fast_rebase(int argc, const char **argv)\n+{\n+\tstruct commit *onto;\n+\tstruct commit *last_commit = NULL, *last_picked_commit = NULL;\n+\tstruct object_id head;\n+\tstruct lock_file lock = LOCK_INIT;\n+\tint clean = 1;\n+\tstruct strvec rev_walk_args = STRVEC_INIT;\n+\tstruct rev_info revs;\n+\tstruct commit *commit;\n+\tstruct merge_options merge_opt;\n+\tstruct tree *next_tree, *base_tree, *head_tree;\n+\tstruct merge_result result;\n+\tstruct strbuf reflog_msg = STRBUF_INIT;\n+\tstruct strbuf branch_name = STRBUF_INIT;\n+\n+\t/*\n+\t * test-tool stuff doesn't set up the git directory by default; need to\n+\t * do that manually.\n+\t */\n+\tsetup_git_directory();\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"-h\")) {\n+\t\tprintf(\"Sorry, I am not a psychiatrist; I can not give you the help you need.  Oh, you meant usage...\\n\");\n+\t\texit(129);\n+\t}\n+\n+\tif (argc != 5 || strcmp(argv[1], \"--onto\"))\n+\t\tdie(\"usage: read the code, figure out how to use it, then do so\");\n+\n+\tonto = peel_committish(argv[2]);\n+\tstrbuf_addf(&branch_name, \"refs/heads/%s\", argv[4]);\n+\n+\t/* Sanity check */\n+\tif (get_oid(\"HEAD\", &head))\n+\t\tdie(_(\"Cannot read HEAD\"));\n+\tassert(oideq(&onto->object.oid, &head));\n+\n+\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n+\tassert(repo_read_index(the_repository) >= 0);\n+\n+\trepo_init_revisions(the_repository, &revs, NULL);\n+\trevs.verbose_header = 1;\n+\trevs.max_parents = 1;\n+\trevs.cherry_mark = 1;\n+\trevs.limited = 1;\n+\trevs.reverse = 1;\n+\trevs.right_only = 1;\n+\trevs.sort_order = REV_SORT_IN_GRAPH_ORDER;\n+\trevs.topo_order = 1;\n+\tstrvec_pushl(&rev_walk_args, \"\", argv[4], \"--not\", argv[3], NULL);\n+\n+\tif (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1)\n+\t\treturn error(_(\"unhandled options\"));\n+\n+\tstrvec_clear(&rev_walk_args);\n+\n+\tif (prepare_revision_walk(&revs) < 0)\n+\t\treturn error(_(\"error preparing revisions\"));\n+\n+\tinit_merge_options(&merge_opt, the_repository);\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_opt.show_rename_progress = 1;\n+\tmerge_opt.branch1 = \"HEAD\";\n+\thead_tree = get_commit_tree(onto);\n+\tresult.tree = head_tree;\n+\tlast_commit = onto;\n+\twhile ((commit = get_revision(&revs))) {\n+\t\tstruct commit *base;\n+\n+\t\tfprintf(stderr, \"Rebasing %s...\\r\",\n+\t\t\toid_to_hex(&commit->object.oid));\n+\t\tassert(commit->parents && !commit->parents->next);\n+\t\tbase = commit->parents->item;\n+\n+\t\tnext_tree = get_commit_tree(commit);\n+\t\tbase_tree = get_commit_tree(base);\n+\n+\t\tmerge_opt.branch2 = short_commit_name(commit);\n+\t\tmerge_opt.ancestor = xstrfmt(\"parent of %s\", merge_opt.branch2);\n+\n+\t\tmerge_incore_nonrecursive(&merge_opt,\n+\t\t\t\t\t  base_tree,\n+\t\t\t\t\t  result.tree,\n+\t\t\t\t\t  next_tree,\n+\t\t\t\t\t  &result);\n+\n+\t\tfree((char*)merge_opt.ancestor);\n+\t\tmerge_opt.ancestor = NULL;\n+\t\tif (!result.clean)\n+\t\t\tdie(\"Aborting: Hit a conflict and restarting is not implemented.\");\n+\t\tlast_picked_commit = commit;\n+\t\tlast_commit = create_commit(result.tree, commit, last_commit);\n+\t}\n+\tfprintf(stderr, \"\\nDone.\\n\");\n+\t/* TODO: There should be some kind of rev_info_free(&revs) call... */\n+\tmemset(&revs, 0, sizeof(revs));\n+\n+\tmerge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean);\n+\n+\tif (result.clean < 0)\n+\t\texit(128);\n+\n+\tstrbuf_addf(&reflog_msg, \"finish rebase %s onto %s\",\n+\t\t    oid_to_hex(&last_picked_commit->object.oid),\n+\t\t    oid_to_hex(&last_commit->object.oid));\n+\tif (update_ref(reflog_msg.buf, branch_name.buf,\n+\t\t       &last_commit->object.oid,\n+\t\t       &last_picked_commit->object.oid,\n+\t\t       REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {\n+\t\terror(_(\"could not update %s\"), argv[4]);\n+\t\tdie(\"Failed to update %s\", argv[4]);\n+\t}\n+\tif (create_symref(\"HEAD\", branch_name.buf, reflog_msg.buf) < 0)\n+\t\tdie(_(\"unable to update HEAD\"));\n+\tstrbuf_release(&reflog_msg);\n+\tstrbuf_release(&branch_name);\n+\n+\tprime_cache_tree(the_repository, the_repository->index, result.tree);\n+\tif (write_locked_index(&the_index, &lock,\n+\t\t\t       COMMIT_LOCK | SKIP_IF_UNCHANGED))\n+\t\tdie(_(\"unable to write %s\"), get_index_file());\n+\treturn (clean == 0);\n+}\ndiff --git a/t/helper/test-tool.c b/t/helper/test-tool.c\nindex a0d3966b29..8bce7db076 100644\n--- a/t/helper/test-tool.c\n+++ b/t/helper/test-tool.c\n@@ -28,6 +28,7 @@ static struct test_cmd cmds[] = {\n \t{ \"dump-split-index\", cmd__dump_split_index },\n \t{ \"dump-untracked-cache\", cmd__dump_untracked_cache },\n \t{ \"example-decorate\", cmd__example_decorate },\n+\t{ \"fast-rebase\", cmd__fast_rebase },\n \t{ \"genrandom\", cmd__genrandom },\n \t{ \"genzeros\", cmd__genzeros },\n \t{ \"hashmap\", cmd__hashmap },\ndiff --git a/t/helper/test-tool.h b/t/helper/test-tool.h\nindex 07034d3f38..fd0cafe5ca 100644\n--- a/t/helper/test-tool.h\n+++ b/t/helper/test-tool.h\n@@ -18,6 +18,7 @@ int cmd__dump_fsmonitor(int argc, const char **argv);\n int cmd__dump_split_index(int argc, const char **argv);\n int cmd__dump_untracked_cache(int argc, const char **argv);\n int cmd__example_decorate(int argc, const char **argv);\n+int cmd__fast_rebase(int argc, const char **argv);\n int cmd__genrandom(int argc, const char **argv);\n int cmd__genzeros(int argc, const char **argv);\n int cmd__hashmap(int argc, const char **argv);\n-- \ngitgitgadget\n\n"},{"id":"408954","messageId":"d7f6a834ab4942b3af329eff9c883fcf7affd7f4.1604360734.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v5.git.git.1604360734.gitgitgadget@gmail.com","subject":"[PATCH v5 2/4] merge-ort-wrappers: new convience wrappers to mimic the old merge API","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T23:45:32Z","receivedAt":"2020-11-02T23:45:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are a few differences between the new API in merge-ort and the old\nAPI in merge-recursive.  While the new API is more flexible, it might\nfeel like more work at times than the old API.  merge-ort-wrappers\ncreates two convenience wrappers taking the exact same arguments as the\nold merge_trees() and merge_recursive() functions and implements them\nvia the new API.  This makes converting existing callsites easier, and\nserves to highlight some of the differences in the API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile             |  1 +\n merge-ort-wrappers.c | 62 ++++++++++++++++++++++++++++++++++++++++++++\n merge-ort-wrappers.h | 25 ++++++++++++++++++\n 3 files changed, 88 insertions(+)\n create mode 100644 merge-ort-wrappers.c\n create mode 100644 merge-ort-wrappers.h\n\ndiff --git a/Makefile b/Makefile\nindex 088770c2ae..382fe73c76 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -922,6 +922,7 @@ LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n 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.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nnew file mode 100644\nindex 0000000000..7eec25f93a\n--- /dev/null\n+++ b/merge-ort-wrappers.c\n@@ -0,0 +1,62 @@\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n+\n+#include \"commit.h\"\n+\n+static int unclean(struct merge_options *opt, struct tree *head)\n+{\n+\t/* Sanity check on repo state; index must match head */\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tif (head && repo_index_has_changes(opt->repo, head, &sb)) {\n+\t\tfprintf(stderr, _(\"Your local changes to the following files would be overwritten by merge:\\n  %s\"),\n+\t\t    sb.buf);\n+\t\tstrbuf_release(&sb);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *merge_base)\n+{\n+\tstruct merge_result result;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tif (oideq(&merge_base->object.oid, &merge->object.oid)) {\n+\t\tprintf(_(\"Already up to date!\"));\n+\t\treturn 1;\n+\t}\n+\n+\tmemset(&result, 0, sizeof(result));\n+\tmerge_incore_nonrecursive(opt, merge_base, head, merge, &result);\n+\tmerge_switch_to_result(opt, head, &result, 1, 1);\n+\n+\treturn result.clean;\n+}\n+\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *side1,\n+\t\t\tstruct commit *side2,\n+\t\t\tstruct commit_list *merge_bases,\n+\t\t\tstruct commit **result)\n+{\n+\tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\n+\tstruct merge_result tmp;\n+\n+\tif (unclean(opt, head))\n+\t\treturn -1;\n+\n+\tmemset(&tmp, 0, sizeof(tmp));\n+\tmerge_incore_recursive(opt, merge_bases, side1, side2, &tmp);\n+\tmerge_switch_to_result(opt, head, &tmp, 1, 1);\n+\t*result = NULL;\n+\n+\treturn tmp.clean;\n+}\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nnew file mode 100644\nindex 0000000000..0c4c57adbb\n--- /dev/null\n+++ b/merge-ort-wrappers.h\n@@ -0,0 +1,25 @@\n+#ifndef MERGE_ORT_WRAPPERS_H\n+#define MERGE_ORT_WRAPPERS_H\n+\n+#include \"merge-recursive.h\"\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * Wrapper mimicking the old merge_trees() function.\n+ */\n+int merge_ort_nonrecursive(struct merge_options *opt,\n+\t\t\t   struct tree *head,\n+\t\t\t   struct tree *merge,\n+\t\t\t   struct tree *common);\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * Wrapper mimicking the old merge_recursive() function.\n+ */\n+int merge_ort_recursive(struct merge_options *opt,\n+\t\t\tstruct commit *h1,\n+\t\t\tstruct commit *h2,\n+\t\t\tstruct commit_list *ancestors,\n+\t\t\tstruct commit **result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408955","messageId":"61217a83bd7ff0ce9016eb4df9ded4fdf29a506c.1604360734.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v5.git.git.1604360734.gitgitgadget@gmail.com","subject":"[PATCH v5 4/4] merge,rebase,revert: select ort or recursive by config or environment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T23:45:34Z","receivedAt":"2020-11-02T23:45:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAllow the testsuite to run where it treats requests for \"recursive\" or\nthe default merge algorithm via consulting the environment variable\nGIT_TEST_MERGE_ALGORITHM which is expected to either be \"recursive\" (the\nold traditional algorithm) or \"ort\" (the new algorithm).\n\nAlso, allow folks to pick the new algorithm via config setting.  It\nturns out builtin/merge.c already had a way to allow users to specify a\ndifferent default merge algorithm: pull.twohead.  Rather odd\nconfiguration name (especially to be in the 'pull' namespace rather than\n'merge') but it's there.  Add that same configuration to rebase,\ncherry-pick, and revert.\n\nThis required updating the various callsites that called merge_trees()\nor merge_recursive() to conditionally call the new API, so this serves\nas another demonstration of what the new API looks and feels like.\nThere are almost certainly some callsites that have not yet been\nmodified to work with the new merge algorithm, but this represents the\nones that I have been testing with thus far.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/merge.c  | 26 +++++++++++++++--\n builtin/rebase.c | 13 ++++++++-\n builtin/revert.c |  7 +++++\n sequencer.c      | 72 ++++++++++++++++++++++++++++++++++++++++--------\n sequencer.h      |  1 +\n 5 files changed, 104 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9d5359edc2..87dfc9bc06 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -28,6 +28,7 @@\n #include \"rerere.h\"\n #include \"help.h\"\n #include \"merge-recursive.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n #include \"fmt-merge-msg.h\"\n@@ -88,6 +89,7 @@ static int no_verify;\n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n \t{ \"octopus\",    DEFAULT_OCTOPUS },\n+\t{ \"ort\",        NO_TRIVIAL },\n \t{ \"resolve\",    0 },\n \t{ \"ours\",       NO_FAST_FORWARD | NO_TRIVIAL },\n \t{ \"subtree\",    NO_FAST_FORWARD | NO_TRIVIAL },\n@@ -159,10 +161,17 @@ static struct strategy *get_strategy(const char *name)\n \tstruct strategy *ret;\n \tstatic struct cmdnames main_cmds, other_cmds;\n \tstatic int loaded;\n+\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n \tif (!name)\n \t\treturn NULL;\n \n+\tif (default_strategy &&\n+\t    !strcmp(default_strategy, \"ort\") &&\n+\t    !strcmp(name, \"recursive\")) {\n+\t\tname = \"ort\";\n+\t}\n+\n \tfor (i = 0; i < ARRAY_SIZE(all_strategy); i++)\n \t\tif (!strcmp(name, all_strategy[i].name))\n \t\t\treturn &all_strategy[i];\n@@ -701,7 +710,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0)\n \t\treturn error(_(\"Unable to write index.\"));\n \n-\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\")) {\n+\tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n+\t    !strcmp(strategy, \"ort\")) {\n \t\tstruct lock_file lock = LOCK_INIT;\n \t\tint clean, x;\n \t\tstruct commit *result;\n@@ -732,8 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\t\tcommit_list_insert(j->item, &reversed);\n \n \t\thold_locked_index(&lock, LOCK_DIE_ON_ERROR);\n-\t\tclean = merge_recursive(&o, head,\n-\t\t\t\tremoteheads->item, reversed, &result);\n+\t\tif (!strcmp(strategy, \"ort\"))\n+\t\t\tclean = merge_ort_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\t    reversed, &result);\n+\t\telse\n+\t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n+\t\t\t\t\t\treversed, &result);\n \t\tif (clean < 0)\n \t\t\texit(128);\n \t\tif (write_locked_index(&the_index, &lock,\n@@ -1264,6 +1278,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (branch)\n \t\tskip_prefix(branch, \"refs/heads/\", &branch);\n \n+\tif (!pull_twohead) {\n+\t\tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n+\t\tif (default_strategy && !strcmp(default_strategy, \"ort\"))\n+\t\t\tpull_twohead = \"ort\";\n+\t}\n+\n \tinit_diff_ui_defaults();\n \tgit_config(git_merge_config, NULL);\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex eeca53382f..4ba5295ddf 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -119,6 +119,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \tstruct replay_opts replay = REPLAY_OPTS_INIT;\n \n \treplay.action = REPLAY_INTERACTIVE_REBASE;\n+\treplay.strategy = NULL;\n \tsequencer_init_config(&replay);\n \n \treplay.signoff = opts->signoff;\n@@ -136,7 +137,12 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)\n \t\t\t\t\topts->committer_date_is_author_date;\n \treplay.ignore_date = opts->ignore_date;\n \treplay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);\n-\treplay.strategy = opts->strategy;\n+\tif (opts->strategy)\n+\t\treplay.strategy = opts->strategy;\n+\telse if (!replay.strategy && replay.default_strategy) {\n+\t\treplay.strategy = replay.default_strategy;\n+\t\treplay.default_strategy = NULL;\n+\t}\n \n \tif (opts->strategy_opts)\n \t\tparse_strategy_opts(&replay, opts->strategy_opts);\n@@ -1771,6 +1777,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\t\t    options.default_backend);\n \t}\n \n+\tif (options.type == REBASE_MERGE &&\n+\t    !options.strategy &&\n+\t    getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\toptions.strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n+\n \tswitch (options.type) {\n \tcase REBASE_MERGE:\n \tcase REBASE_PRESERVE_MERGES:\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex f61cc5d82c..5b01a9baca 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -172,6 +172,11 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t\t\t\tNULL);\n \t}\n \n+\tif (!opts->strategy && opts->default_strategy) {\n+\t\topts->strategy = opts->default_strategy;\n+\t\topts->default_strategy = NULL;\n+\t}\n+\n \tif (opts->allow_ff)\n \t\tverify_opt_compatible(me, \"--ff\",\n \t\t\t\t\"--signoff\", opts->signoff,\n@@ -202,6 +207,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t/* These option values will be free()d */\n \topts->gpg_sign = xstrdup_or_null(opts->gpg_sign);\n \topts->strategy = xstrdup_or_null(opts->strategy);\n+\tif (!opts->strategy && getenv(\"GIT_TEST_MERGE_ALGORITHM\"))\n+\t\topts->strategy = xstrdup(getenv(\"GIT_TEST_MERGE_ALGORITHM\"));\n \n \tif (cmd == 'q') {\n \t\tint ret = sequencer_remove_state(opts);\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..6b6c15c357 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -14,7 +14,8 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"rerere.h\"\n-#include \"merge-recursive.h\"\n+#include \"merge-ort.h\"\n+#include \"merge-ort-wrappers.h\"\n #include \"refs.h\"\n #include \"strvec.h\"\n #include \"quote.h\"\n@@ -204,6 +205,20 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (!opts->default_strategy && !strcmp(k, \"pull.twohead\")) {\n+\t\tint ret = git_config_string((const char**)&opts->default_strategy, k, v);\n+\t\tif (ret == 0) {\n+\t\t\t/*\n+\t\t\t * pull.twohead is allowed to be multi-valued; we only\n+\t\t\t * care about the first value.\n+\t\t\t */\n+\t\t\tchar *tmp = strchr(opts->default_strategy, ' ');\n+\t\t\tif (tmp)\n+\t\t\t\t*tmp = '\\0';\n+\t\t}\n+\t\treturn ret;\n+\t}\n+\n \tstatus = git_gpg_config(k, v, NULL);\n \tif (status)\n \t\treturn status;\n@@ -317,6 +332,7 @@ int sequencer_remove_state(struct replay_opts *opts)\n \tfree(opts->committer_name);\n \tfree(opts->committer_email);\n \tfree(opts->gpg_sign);\n+\tfree(opts->default_strategy);\n \tfree(opts->strategy);\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tfree(opts->xopts[i]);\n@@ -595,8 +611,9 @@ static int do_recursive_merge(struct repository *r,\n \t\t\t      struct replay_opts *opts)\n {\n \tstruct merge_options o;\n+\tstruct merge_result result;\n \tstruct tree *next_tree, *base_tree, *head_tree;\n-\tint clean;\n+\tint clean, show_output;\n \tint i;\n \tstruct lock_file index_lock = LOCK_INIT;\n \n@@ -620,12 +637,27 @@ static int do_recursive_merge(struct repository *r,\n \tfor (i = 0; i < opts->xopts_nr; i++)\n \t\tparse_merge_opt(&o, opts->xopts[i]);\n \n-\tclean = merge_trees(&o,\n-\t\t\t    head_tree,\n-\t\t\t    next_tree, base_tree);\n-\tif (is_rebase_i(opts) && clean <= 0)\n-\t\tfputs(o.obuf.buf, stdout);\n-\tstrbuf_release(&o.obuf);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\tmemset(&result, 0, sizeof(result));\n+\t\tmerge_incore_nonrecursive(&o, base_tree, head_tree, next_tree,\n+\t\t\t\t\t    &result);\n+\t\tshow_output = !is_rebase_i(opts) || !result.clean;\n+\t\t/*\n+\t\t * TODO: merge_switch_to_result will update index/working tree;\n+\t\t * we only really want to do that if !result.clean || this is\n+\t\t * the final patch to be picked.  But determining this is the\n+\t\t * final patch would take some work, and \"head_tree\" would need\n+\t\t * to be replace with the tree the index matched before we\n+\t\t * started doing any picks.\n+\t\t */\n+\t\tmerge_switch_to_result(&o, head_tree, &result, 1, show_output);\n+\t\tclean = result.clean;\n+\t} else {\n+\t\tclean = merge_trees(&o, head_tree, next_tree, base_tree);\n+\t\tif (is_rebase_i(opts) && clean <= 0)\n+\t\t\tfputs(o.obuf.buf, stdout);\n+\t\tstrbuf_release(&o.obuf);\n+\t}\n \tif (clean < 0) {\n \t\trollback_lock_file(&index_lock);\n \t\treturn clean;\n@@ -1991,7 +2023,10 @@ static int do_pick_commit(struct repository *r,\n \n \tif (is_rebase_i(opts) && write_author_script(msg.message) < 0)\n \t\tres = -1;\n-\telse if (!opts->strategy || !strcmp(opts->strategy, \"recursive\") || command == TODO_REVERT) {\n+\telse if (!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\") ||\n+\t\t command == TODO_REVERT) {\n \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n \t\t\t\t\t &head, &msgbuf, opts);\n \t\tif (res < 0)\n@@ -3485,7 +3520,9 @@ static int do_merge(struct repository *r,\n \tstruct commit_list *bases, *j, *reversed = NULL;\n \tstruct commit_list *to_merge = NULL, **tail = &to_merge;\n \tconst char *strategy = !opts->xopts_nr &&\n-\t\t(!opts->strategy || !strcmp(opts->strategy, \"recursive\")) ?\n+\t\t(!opts->strategy ||\n+\t\t !strcmp(opts->strategy, \"recursive\") ||\n+\t\t !strcmp(opts->strategy, \"ort\")) ?\n \t\tNULL : opts->strategy;\n \tstruct merge_options o;\n \tint merge_arg_len, oneline_offset, can_fast_forward, ret, k;\n@@ -3722,7 +3759,20 @@ static int do_merge(struct repository *r,\n \to.branch2 = ref_name.buf;\n \to.buffer_output = 2;\n \n-\tret = merge_recursive(&o, head_commit, merge_commit, reversed, &i);\n+\tif (opts->strategy && !strcmp(opts->strategy, \"ort\")) {\n+\t\t/*\n+\t\t * TODO: Should use merge_incore_recursive() and\n+\t\t * merge_switch_to_result(), skipping the call to\n+\t\t * merge_switch_to_result() when we don't actually need to\n+\t\t * update the index and working copy immediately.\n+\t\t */\n+\t\tret = merge_ort_recursive(&o,\n+\t\t\t\t\t  head_commit, merge_commit, reversed,\n+\t\t\t\t\t  &i);\n+\t} else {\n+\t\tret = merge_recursive(&o, head_commit, merge_commit, reversed,\n+\t\t\t\t      &i);\n+\t}\n \tif (ret <= 0)\n \t\tfputs(o.obuf.buf, stdout);\n \tstrbuf_release(&o.obuf);\ndiff --git a/sequencer.h b/sequencer.h\nindex b2a501e445..020b7fa118 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -57,6 +57,7 @@ struct replay_opts {\n \tint explicit_cleanup;\n \n \t/* Merge strategy */\n+\tchar *default_strategy;  /* from config options */\n \tchar *strategy;\n \tchar **xopts;\n \tsize_t xopts_nr, xopts_alloc;\n-- \ngitgitgadget\n"},{"id":"408956","messageId":"3357ea415e3437966f15bf73fbbeb21cda3df592.1604360734.git.gitgitgadget@gmail.com","threadId":"54475","inReplyTo":"pull.895.v5.git.git.1604360734.gitgitgadget@gmail.com","subject":"[PATCH v5 1/4] merge-ort: barebones API of new merge strategy with empty implementation","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T23:45:31Z","receivedAt":"2020-11-02T23:45:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis is the beginning of a new merge strategy.  While there are some API\ndifferences, and the implementation has some differences in behavior, it\nis essentially meant as an eventual drop-in replacement for\nmerge-recursive.c.  However, it is being built to exist side-by-side\nwith merge-recursive so that we have plenty of time to find out how\nthose differences pan out in the real world while people can still fall\nback to merge-recursive.  (Also, I intend to avoid modifying\nmerge-recursive during this process, to keep it stable.)\n\nThe primary difference noticable here is that the updating of the\nworking tree and index is not done simultaneously with the merge\nalgorithm, but is a separate post-processing step.  The new API is\ndesigned so that one can do repeated merges (e.g. during a rebase or\ncherry-pick) and only update the index and working tree one time at the\nend instead of updating it with every intermediate result.  Also, one\ncan perform a merge between two branches, neither of which match the\nindex or the working tree, without clobbering the index or working tree.\n\nThe next three commits will demonstrate various uses of this new API.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile    |  1 +\n merge-ort.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++\n merge-ort.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 111 insertions(+)\n create mode 100644 merge-ort.c\n create mode 100644 merge-ort.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..088770c2ae 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -921,6 +921,7 @@ LIB_OBJS += mailmap.o\n LIB_OBJS += match-trees.o\n LIB_OBJS += mem-pool.o\n LIB_OBJS += merge-blobs.o\n+LIB_OBJS += merge-ort.o\n LIB_OBJS += merge-recursive.o\n LIB_OBJS += merge.o\n LIB_OBJS += mergesort.o\ndiff --git a/merge-ort.c b/merge-ort.c\nnew file mode 100644\nindex 0000000000..b487901d3e\n--- /dev/null\n+++ b/merge-ort.c\n@@ -0,0 +1,52 @@\n+/*\n+ * \"Ostensibly Recursive's Twin\" merge strategy, or \"ort\" for short.  Meant\n+ * as a drop-in replacement for the \"recursive\" merge strategy, allowing one\n+ * to replace\n+ *\n+ *   git merge [-s recursive]\n+ *\n+ * with\n+ *\n+ *   git merge -s ort\n+ *\n+ * Note: git's parser allows the space between '-s' and its argument to be\n+ * missing.  (Should I have backronymed \"ham\", \"alsa\", \"kip\", \"nap, \"alvo\",\n+ * \"cale\", \"peedy\", or \"ins\" instead of \"ort\"?)\n+ */\n+\n+#include \"cache.h\"\n+#include \"merge-ort.h\"\n+\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs)\n+{\n+\tdie(\"Not yet implemented\");\n+\tmerge_finalize(opt, result);\n+}\n+\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_incore_nonrecursive(struct merge_options *opt,\n+\t\t\t       struct tree *merge_base,\n+\t\t\t       struct tree *side1,\n+\t\t\t       struct tree *side2,\n+\t\t\t       struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\n+\n+void merge_incore_recursive(struct merge_options *opt,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct commit *side1,\n+\t\t\t    struct commit *side2,\n+\t\t\t    struct merge_result *result)\n+{\n+\tdie(\"Not yet implemented\");\n+}\ndiff --git a/merge-ort.h b/merge-ort.h\nnew file mode 100644\nindex 0000000000..74adccad16\n--- /dev/null\n+++ b/merge-ort.h\n@@ -0,0 +1,58 @@\n+#ifndef MERGE_ORT_H\n+#define MERGE_ORT_H\n+\n+#include \"merge-recursive.h\"\n+\n+struct commit;\n+struct tree;\n+\n+struct merge_result {\n+\t/* Whether the merge is clean */\n+\tint clean;\n+\n+\t/*\n+\t * Result of merge.  If !clean, represents what would go in worktree\n+\t * (thus possibly including files containing conflict markers).\n+\t */\n+\tstruct tree *tree;\n+\n+\t/*\n+\t * Additional metadata used by merge_switch_to_result() or future calls\n+\t * to merge_incore_*().  Includes data needed to update the index (if\n+\t * !clean) and to print \"CONFLICT\" messages.  Not for external use.\n+\t */\n+\tvoid *priv;\n+};\n+\n+/*\n+ * rename-detecting three-way merge with recursive ancestor consolidation.\n+ * working tree and index are untouched.\n+ */\n+void merge_incore_recursive(struct merge_options *opt,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct commit *side1,\n+\t\t\t    struct commit *side2,\n+\t\t\t    struct merge_result *result);\n+\n+/*\n+ * rename-detecting three-way merge, no recursion.\n+ * working tree and index are untouched.\n+ */\n+void merge_incore_nonrecursive(struct merge_options *opt,\n+\t\t\t       struct tree *merge_base,\n+\t\t\t       struct tree *side1,\n+\t\t\t       struct tree *side2,\n+\t\t\t       struct merge_result *result);\n+\n+/* Update the working tree and index from head to result after incore merge */\n+void merge_switch_to_result(struct merge_options *opt,\n+\t\t\t    struct tree *head,\n+\t\t\t    struct merge_result *result,\n+\t\t\t    int update_worktree_and_index,\n+\t\t\t    int display_update_msgs);\n+\n+/* Do needed cleanup when not calling merge_switch_to_result() */\n+void merge_finalize(struct merge_options *opt,\n+\t\t    struct merge_result *result);\n+\n+#endif\n-- \ngitgitgadget\n\n"},{"id":"408973","messageId":"xmqqtuu7p7z4.fsf@gitster.c.googlers.com","threadId":"54475","inReplyTo":"pull.895.v5.git.git.1604360734.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-03T01:03:27Z","receivedAt":"2020-11-03T01:03:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> In this series, I show the new merge API I have developed in merge-ort, and\n> show how it differs from that provided by merge-recursive. I do this in four\n> steps, each corresponding to a patch.\n>\n> Changes since v4:\n>\n>  * Fix a bug where 'cherry-pick --continue' would report 'fatal:\n>    cherry-pick: --strategy cannot be used with --continue' when pull.twohead\n>    was set to ort (found by user of internal deployment at $DAYJOB)\n\nWill replace.  Thanks.\n"},{"id":"409316","messageId":"CABPp-BFeJFXz2=OyQr4chXrdvL0=S9Z57BfzD5xvKExtQAdjuA@mail.gmail.com","threadId":"54475","inReplyTo":"CABPp-BGELtrMa4TK-dkKWxrQ_iQHyEo1yaJBGC_ZyFWG7-9gKg@mail.gmail.com","subject":"Re: [PATCH v4 0/4] Beginning of new merge strategy: New API, empty implementation","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-07T06:09:53Z","receivedAt":"2020-11-07T06:26:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi Jake,\n\nOn Mon, Nov 2, 2020 at 10:52 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Mon, Nov 2, 2020 at 1:28 AM Jacob Keller <jacob.keller@gmail.com> wrote:\n> >\n> > On Thu, Oct 29, 2020 at 1:34 PM Elijah Newren via GitGitGadget\n> > <gitgitgadget@gmail.com> wrote:\n> > >\n> > > In this series, I show the new merge API I have developed in merge-ort, and\n> > > show how it differs from that provided by merge-recursive. I do this in four\n> > > steps, each corresponding to a patch.\n> > >\n> >\n> > I'm definitely excited by this project. I'm curious if you have any\n> > further implementation as a WIP that could be played with to see the\n> > end result of the new merging?\n> >\n> > I definitely like this approach where you work in smaller increments\n> > to make the implementation easier to review!\n>\n> I usually keep the 'ort' branch of https://github.com/newren/git\n> functional (no promises, though).  It has lots of ifdefs, super ugly\n> commits, todos & fixmes, and random additional (non-code) files where\n> I was tracking various things I was working on, so the code and the\n> tree may not be super readable, but it should be usable (and passes\n> all the tests) -- just set pull.twohead=ort in your git config, or set\n> the environment variable GIT_TEST_MERGE_ALGORITHM=ort.\n>\n> One warning: git cherry-pick --continue fails with \"Cannot specify\n> both --continue and --strategy\"; my handling to set a --strategy\n> option when pull.twohead was set apparently needs some tweaks.  If you\n> spot any bugs or other issues, let me know.\n\nIn addition to fixing this \"cherry-pick --continue\" bug last Monday, I\ndiscovered a bug yesterday while re-merging all the merge commits in\nthe linux kernel causing it to fail an assertion.  I'm surprised I\nhadn't hit that sooner, but if you're testing it out you may want to\nupdate your copy to the latest version of the 'ort' branch (make sure\nit has commit 067e5c1a38, \"merge-ort: fix bug with cached_target_names\nnot being initialized in redos\", 2020-11-06).\n"}]}